herb 0.10.1 → 0.11.1

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 (793) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +17 -7
  3. data/README.md +36 -119
  4. data/Rakefile +2 -11
  5. data/config/action_view_helpers/actioncable/action_cable_helper/action_cable_meta_tag.yml +12 -8
  6. data/config/action_view_helpers/actionpack/content_security_policy/content_security_policy?.yml +9 -6
  7. data/config/action_view_helpers/actionpack/content_security_policy/content_security_policy_nonce.yml +9 -6
  8. data/config/action_view_helpers/actionpack/polymorphic_routes/polymorphic_path.yml +20 -12
  9. data/config/action_view_helpers/actionpack/polymorphic_routes/polymorphic_url.yml +30 -18
  10. data/config/action_view_helpers/actionpack/request_forgery_protection/form_authenticity_token.yml +15 -9
  11. data/config/action_view_helpers/actionpack/request_forgery_protection/protect_against_forgery?.yml +9 -6
  12. data/config/action_view_helpers/actiontext/content_helper/render_action_text_content.yml +15 -9
  13. data/config/action_view_helpers/actiontext/content_helper/sanitize_action_text_content.yml +15 -9
  14. data/config/action_view_helpers/actiontext/tag_helper/rich_textarea_tag.yml +34 -22
  15. data/config/action_view_helpers/actionview/active_model_helper/error_message.yml +9 -6
  16. data/config/action_view_helpers/actionview/active_model_helper/error_wrapping.yml +9 -6
  17. data/config/action_view_helpers/actionview/active_model_helper/object.yml +9 -6
  18. data/config/action_view_helpers/actionview/asset_tag_helper/apply_stylesheet_media_default.yml +9 -6
  19. data/config/action_view_helpers/actionview/asset_tag_helper/audio_tag.yml +40 -27
  20. data/config/action_view_helpers/actionview/asset_tag_helper/auto_discovery_link_tag.yml +37 -23
  21. data/config/action_view_helpers/actionview/asset_tag_helper/auto_include_nonce.yml +9 -6
  22. data/config/action_view_helpers/actionview/asset_tag_helper/auto_include_nonce_for_scripts.yml +9 -6
  23. data/config/action_view_helpers/actionview/asset_tag_helper/auto_include_nonce_for_styles.yml +9 -6
  24. data/config/action_view_helpers/actionview/asset_tag_helper/favicon_link_tag.yml +32 -22
  25. data/config/action_view_helpers/actionview/asset_tag_helper/image_decoding.yml +9 -6
  26. data/config/action_view_helpers/actionview/asset_tag_helper/image_loading.yml +9 -6
  27. data/config/action_view_helpers/actionview/asset_tag_helper/image_tag.yml +67 -46
  28. data/config/action_view_helpers/actionview/asset_tag_helper/javascript_include_tag.yml +58 -39
  29. data/config/action_view_helpers/actionview/asset_tag_helper/picture_tag.yml +22 -14
  30. data/config/action_view_helpers/actionview/asset_tag_helper/preload_link_tag.yml +45 -30
  31. data/config/action_view_helpers/actionview/asset_tag_helper/preload_links_header.yml +9 -6
  32. data/config/action_view_helpers/actionview/asset_tag_helper/stylesheet_link_tag.yml +49 -30
  33. data/config/action_view_helpers/actionview/asset_tag_helper/video_tag.yml +53 -35
  34. data/config/action_view_helpers/actionview/asset_url_helper/asset_path.yml +16 -10
  35. data/config/action_view_helpers/actionview/asset_url_helper/asset_url.yml +16 -10
  36. data/config/action_view_helpers/actionview/asset_url_helper/audio_path.yml +16 -10
  37. data/config/action_view_helpers/actionview/asset_url_helper/audio_url.yml +16 -10
  38. data/config/action_view_helpers/actionview/asset_url_helper/compute_asset_extname.yml +9 -6
  39. data/config/action_view_helpers/actionview/asset_url_helper/compute_asset_host.yml +9 -6
  40. data/config/action_view_helpers/actionview/asset_url_helper/compute_asset_path.yml +9 -6
  41. data/config/action_view_helpers/actionview/asset_url_helper/font_path.yml +16 -10
  42. data/config/action_view_helpers/actionview/asset_url_helper/font_url.yml +16 -10
  43. data/config/action_view_helpers/actionview/asset_url_helper/image_path.yml +16 -10
  44. data/config/action_view_helpers/actionview/asset_url_helper/image_url.yml +16 -10
  45. data/config/action_view_helpers/actionview/asset_url_helper/javascript_path.yml +16 -10
  46. data/config/action_view_helpers/actionview/asset_url_helper/javascript_url.yml +16 -10
  47. data/config/action_view_helpers/actionview/asset_url_helper/public_compute_asset_path.yml +9 -6
  48. data/config/action_view_helpers/actionview/asset_url_helper/stylesheet_path.yml +16 -10
  49. data/config/action_view_helpers/actionview/asset_url_helper/stylesheet_url.yml +16 -10
  50. data/config/action_view_helpers/actionview/asset_url_helper/video_path.yml +16 -10
  51. data/config/action_view_helpers/actionview/asset_url_helper/video_url.yml +16 -10
  52. data/config/action_view_helpers/actionview/atom_feed_helper/atom_feed.yml +52 -27
  53. data/config/action_view_helpers/actionview/cache_helper/cache.yml +25 -15
  54. data/config/action_view_helpers/actionview/cache_helper/cache_fragment_name.yml +9 -6
  55. data/config/action_view_helpers/actionview/cache_helper/cache_if.yml +20 -12
  56. data/config/action_view_helpers/actionview/cache_helper/cache_unless.yml +20 -12
  57. data/config/action_view_helpers/actionview/cache_helper/caching_predicate.yml +9 -6
  58. data/config/action_view_helpers/actionview/cache_helper/digest_path_from_template.yml +9 -6
  59. data/config/action_view_helpers/actionview/cache_helper/fragment_for.yml +9 -6
  60. data/config/action_view_helpers/actionview/cache_helper/fragment_name_with_digest.yml +9 -6
  61. data/config/action_view_helpers/actionview/cache_helper/read_fragment_for.yml +9 -6
  62. data/config/action_view_helpers/actionview/cache_helper/uncacheable.yml +9 -6
  63. data/config/action_view_helpers/actionview/cache_helper/write_fragment_for.yml +9 -6
  64. data/config/action_view_helpers/actionview/capture_helper/capture.yml +9 -6
  65. data/config/action_view_helpers/actionview/capture_helper/content_for.yml +20 -12
  66. data/config/action_view_helpers/actionview/capture_helper/content_for_predicate.yml +15 -9
  67. data/config/action_view_helpers/actionview/capture_helper/provide.yml +15 -9
  68. data/config/action_view_helpers/actionview/capture_helper/with_output_buffer.yml +9 -6
  69. data/config/action_view_helpers/actionview/content_exfiltration_prevention_helper/prepend_content_exfiltration_prevention.yml +9 -6
  70. data/config/action_view_helpers/actionview/content_exfiltration_prevention_helper/prevent_content_exfiltration.yml +9 -6
  71. data/config/action_view_helpers/actionview/controller_helper/action_name.yml +9 -6
  72. data/config/action_view_helpers/actionview/controller_helper/assign_controller.yml +9 -6
  73. data/config/action_view_helpers/actionview/controller_helper/controller.yml +9 -6
  74. data/config/action_view_helpers/actionview/controller_helper/controller_name.yml +9 -6
  75. data/config/action_view_helpers/actionview/controller_helper/controller_path.yml +9 -6
  76. data/config/action_view_helpers/actionview/controller_helper/cookies.yml +9 -6
  77. data/config/action_view_helpers/actionview/controller_helper/flash.yml +9 -6
  78. data/config/action_view_helpers/actionview/controller_helper/headers.yml +9 -6
  79. data/config/action_view_helpers/actionview/controller_helper/logger.yml +9 -6
  80. data/config/action_view_helpers/actionview/controller_helper/params.yml +9 -6
  81. data/config/action_view_helpers/actionview/controller_helper/request.yml +9 -6
  82. data/config/action_view_helpers/actionview/controller_helper/request_forgery_protection_token.yml +9 -6
  83. data/config/action_view_helpers/actionview/controller_helper/response.yml +9 -6
  84. data/config/action_view_helpers/actionview/controller_helper/session.yml +9 -6
  85. data/config/action_view_helpers/actionview/csp_helper/csp_meta_tag.yml +18 -11
  86. data/config/action_view_helpers/actionview/csrf_helper/csrf_meta_tags.yml +16 -10
  87. data/config/action_view_helpers/actionview/date_helper/date_select.yml +85 -51
  88. data/config/action_view_helpers/actionview/date_helper/datetime_select.yml +65 -39
  89. data/config/action_view_helpers/actionview/date_helper/distance_of_time_in_words.yml +30 -18
  90. data/config/action_view_helpers/actionview/date_helper/relative_time_in_words.yml +35 -21
  91. data/config/action_view_helpers/actionview/date_helper/select_date.yml +50 -30
  92. data/config/action_view_helpers/actionview/date_helper/select_datetime.yml +65 -39
  93. data/config/action_view_helpers/actionview/date_helper/select_day.yml +40 -24
  94. data/config/action_view_helpers/actionview/date_helper/select_hour.yml +50 -30
  95. data/config/action_view_helpers/actionview/date_helper/select_minute.yml +40 -24
  96. data/config/action_view_helpers/actionview/date_helper/select_month.yml +60 -36
  97. data/config/action_view_helpers/actionview/date_helper/select_second.yml +35 -21
  98. data/config/action_view_helpers/actionview/date_helper/select_time.yml +55 -33
  99. data/config/action_view_helpers/actionview/date_helper/select_year.yml +45 -27
  100. data/config/action_view_helpers/actionview/date_helper/time_ago_in_words.yml +21 -13
  101. data/config/action_view_helpers/actionview/date_helper/time_select.yml +60 -36
  102. data/config/action_view_helpers/actionview/date_helper/time_tag.yml +32 -20
  103. data/config/action_view_helpers/actionview/debug_helper/debug.yml +15 -9
  104. data/config/action_view_helpers/actionview/form_builder/button.yml +60 -0
  105. data/config/action_view_helpers/actionview/form_builder/submit.yml +48 -0
  106. data/config/action_view_helpers/actionview/form_helper/apply_form_for_options.yml +9 -6
  107. data/config/action_view_helpers/actionview/form_helper/checkbox.yml +41 -25
  108. data/config/action_view_helpers/actionview/form_helper/color_field.yml +25 -15
  109. data/config/action_view_helpers/actionview/form_helper/date_field.yml +35 -21
  110. data/config/action_view_helpers/actionview/form_helper/datetime_field.yml +41 -25
  111. data/config/action_view_helpers/actionview/form_helper/email_field.yml +25 -15
  112. data/config/action_view_helpers/actionview/form_helper/emitted_hidden_id.yml +9 -6
  113. data/config/action_view_helpers/actionview/form_helper/fields.yml +37 -18
  114. data/config/action_view_helpers/actionview/form_helper/fields_for.yml +33 -15
  115. data/config/action_view_helpers/actionview/form_helper/file_field.yml +45 -27
  116. data/config/action_view_helpers/actionview/form_helper/form_for.yml +45 -24
  117. data/config/action_view_helpers/actionview/form_helper/form_with.yml +56 -30
  118. data/config/action_view_helpers/actionview/form_helper/form_with_generates_ids.yml +9 -6
  119. data/config/action_view_helpers/actionview/form_helper/form_with_generates_remote_forms.yml +9 -6
  120. data/config/action_view_helpers/actionview/form_helper/hidden_field.yml +25 -15
  121. data/config/action_view_helpers/actionview/form_helper/label.yml +52 -27
  122. data/config/action_view_helpers/actionview/form_helper/month_field.yml +25 -15
  123. data/config/action_view_helpers/actionview/form_helper/multipart.yml +9 -6
  124. data/config/action_view_helpers/actionview/form_helper/multiple_file_field_include_hidden.yml +9 -6
  125. data/config/action_view_helpers/actionview/form_helper/number_field.yml +45 -27
  126. data/config/action_view_helpers/actionview/form_helper/password_field.yml +25 -15
  127. data/config/action_view_helpers/actionview/form_helper/radio_button.yml +30 -18
  128. data/config/action_view_helpers/actionview/form_helper/range_field.yml +45 -27
  129. data/config/action_view_helpers/actionview/form_helper/search_field.yml +25 -15
  130. data/config/action_view_helpers/actionview/form_helper/telephone_field.yml +26 -16
  131. data/config/action_view_helpers/actionview/form_helper/text_field.yml +25 -15
  132. data/config/action_view_helpers/actionview/form_helper/textarea.yml +31 -19
  133. data/config/action_view_helpers/actionview/form_helper/time_field.yml +40 -24
  134. data/config/action_view_helpers/actionview/form_helper/url_field.yml +25 -15
  135. data/config/action_view_helpers/actionview/form_helper/week_field.yml +25 -15
  136. data/config/action_view_helpers/actionview/form_options_helper/collection_checkboxes.yml +58 -31
  137. data/config/action_view_helpers/actionview/form_options_helper/collection_radio_buttons.yml +57 -30
  138. data/config/action_view_helpers/actionview/form_options_helper/collection_select.yml +65 -39
  139. data/config/action_view_helpers/actionview/form_options_helper/grouped_collection_select.yml +65 -39
  140. data/config/action_view_helpers/actionview/form_options_helper/grouped_options_for_select.yml +35 -21
  141. data/config/action_view_helpers/actionview/form_options_helper/option_groups_from_collection_for_select.yml +40 -24
  142. data/config/action_view_helpers/actionview/form_options_helper/options_for_select.yml +20 -12
  143. data/config/action_view_helpers/actionview/form_options_helper/options_from_collection_for_select.yml +30 -18
  144. data/config/action_view_helpers/actionview/form_options_helper/select.yml +60 -36
  145. data/config/action_view_helpers/actionview/form_options_helper/time_zone_options_for_select.yml +25 -15
  146. data/config/action_view_helpers/actionview/form_options_helper/time_zone_select.yml +50 -30
  147. data/config/action_view_helpers/actionview/form_options_helper/weekday_options_for_select.yml +30 -18
  148. data/config/action_view_helpers/actionview/form_options_helper/weekday_select.yml +30 -18
  149. data/config/action_view_helpers/actionview/form_tag_helper/button_tag.yml +39 -21
  150. data/config/action_view_helpers/actionview/form_tag_helper/checkbox_tag.yml +33 -21
  151. data/config/action_view_helpers/actionview/form_tag_helper/color_field_tag.yml +26 -17
  152. data/config/action_view_helpers/actionview/form_tag_helper/date_field_tag.yml +27 -17
  153. data/config/action_view_helpers/actionview/form_tag_helper/datetime_field_tag.yml +28 -18
  154. data/config/action_view_helpers/actionview/form_tag_helper/default_enforce_utf8.yml +9 -6
  155. data/config/action_view_helpers/actionview/form_tag_helper/email_field_tag.yml +27 -17
  156. data/config/action_view_helpers/actionview/form_tag_helper/embed_authenticity_token_in_remote_forms.yml +9 -6
  157. data/config/action_view_helpers/actionview/form_tag_helper/field_id.yml +30 -18
  158. data/config/action_view_helpers/actionview/form_tag_helper/field_name.yml +30 -18
  159. data/config/action_view_helpers/actionview/form_tag_helper/field_set_tag.yml +23 -15
  160. data/config/action_view_helpers/actionview/form_tag_helper/file_field_tag.yml +35 -22
  161. data/config/action_view_helpers/actionview/form_tag_helper/form_tag.yml +38 -24
  162. data/config/action_view_helpers/actionview/form_tag_helper/hidden_field_tag.yml +27 -17
  163. data/config/action_view_helpers/actionview/form_tag_helper/image_submit_tag.yml +31 -21
  164. data/config/action_view_helpers/actionview/form_tag_helper/label_tag.yml +33 -17
  165. data/config/action_view_helpers/actionview/form_tag_helper/month_field_tag.yml +27 -17
  166. data/config/action_view_helpers/actionview/form_tag_helper/number_field_tag.yml +47 -29
  167. data/config/action_view_helpers/actionview/form_tag_helper/password_field_tag.yml +27 -17
  168. data/config/action_view_helpers/actionview/form_tag_helper/radio_button_tag.yml +32 -20
  169. data/config/action_view_helpers/actionview/form_tag_helper/range_field_tag.yml +47 -29
  170. data/config/action_view_helpers/actionview/form_tag_helper/search_field_tag.yml +27 -17
  171. data/config/action_view_helpers/actionview/form_tag_helper/select_tag.yml +47 -29
  172. data/config/action_view_helpers/actionview/form_tag_helper/submit_tag.yml +33 -21
  173. data/config/action_view_helpers/actionview/form_tag_helper/telephone_field_tag.yml +28 -18
  174. data/config/action_view_helpers/actionview/form_tag_helper/text_field_tag.yml +27 -17
  175. data/config/action_view_helpers/actionview/form_tag_helper/textarea_tag.yml +31 -20
  176. data/config/action_view_helpers/actionview/form_tag_helper/time_field_tag.yml +27 -17
  177. data/config/action_view_helpers/actionview/form_tag_helper/url_field_tag.yml +27 -17
  178. data/config/action_view_helpers/actionview/form_tag_helper/utf8_enforcer_tag.yml +9 -6
  179. data/config/action_view_helpers/actionview/form_tag_helper/week_field_tag.yml +28 -18
  180. data/config/action_view_helpers/actionview/javascript_helper/escape_javascript.yml +16 -10
  181. data/config/action_view_helpers/actionview/javascript_helper/javascript_cdata_section.yml +9 -6
  182. data/config/action_view_helpers/actionview/javascript_helper/javascript_tag.yml +41 -27
  183. data/config/action_view_helpers/actionview/number_helper/number_to_currency.yml +29 -18
  184. data/config/action_view_helpers/actionview/number_helper/number_to_human.yml +25 -15
  185. data/config/action_view_helpers/actionview/number_helper/number_to_human_size.yml +20 -12
  186. data/config/action_view_helpers/actionview/number_helper/number_to_percentage.yml +20 -12
  187. data/config/action_view_helpers/actionview/number_helper/number_to_phone.yml +33 -20
  188. data/config/action_view_helpers/actionview/number_helper/number_with_delimiter.yml +21 -13
  189. data/config/action_view_helpers/actionview/number_helper/number_with_precision.yml +25 -15
  190. data/config/action_view_helpers/actionview/output_safety_helper/raw.yml +15 -9
  191. data/config/action_view_helpers/actionview/output_safety_helper/safe_join.yml +20 -12
  192. data/config/action_view_helpers/actionview/output_safety_helper/to_sentence.yml +24 -15
  193. data/config/action_view_helpers/actionview/record_identifier/dom_class.yml +20 -12
  194. data/config/action_view_helpers/actionview/record_identifier/dom_id.yml +20 -12
  195. data/config/action_view_helpers/actionview/record_identifier/dom_target.yml +15 -9
  196. data/config/action_view_helpers/actionview/record_identifier/model_name_from_record_or_class.yml +9 -6
  197. data/config/action_view_helpers/actionview/rendering_helper/render.yml +108 -22
  198. data/config/action_view_helpers/actionview/sanitize_helper/sanitize.yml +25 -15
  199. data/config/action_view_helpers/actionview/sanitize_helper/sanitize_css.yml +15 -9
  200. data/config/action_view_helpers/actionview/sanitize_helper/sanitizer_vendor.yml +9 -6
  201. data/config/action_view_helpers/actionview/sanitize_helper/strip_links.yml +15 -9
  202. data/config/action_view_helpers/actionview/sanitize_helper/strip_tags.yml +15 -9
  203. data/config/action_view_helpers/actionview/tag_helper/cdata_section.yml +15 -9
  204. data/config/action_view_helpers/actionview/tag_helper/content_tag.yml +59 -38
  205. data/config/action_view_helpers/actionview/tag_helper/escape_once.yml +15 -9
  206. data/config/action_view_helpers/actionview/tag_helper/tag.yml +54 -31
  207. data/config/action_view_helpers/actionview/tag_helper/token_list.yml +16 -10
  208. data/config/action_view_helpers/actionview/text_helper/concat.yml +15 -9
  209. data/config/action_view_helpers/actionview/text_helper/current_cycle.yml +15 -9
  210. data/config/action_view_helpers/actionview/text_helper/cycle.yml +23 -14
  211. data/config/action_view_helpers/actionview/text_helper/excerpt.yml +33 -20
  212. data/config/action_view_helpers/actionview/text_helper/highlight.yml +37 -18
  213. data/config/action_view_helpers/actionview/text_helper/pluralize.yml +30 -18
  214. data/config/action_view_helpers/actionview/text_helper/reset_cycle.yml +15 -9
  215. data/config/action_view_helpers/actionview/text_helper/safe_concat.yml +9 -6
  216. data/config/action_view_helpers/actionview/text_helper/simple_format.yml +33 -20
  217. data/config/action_view_helpers/actionview/text_helper/truncate.yml +38 -23
  218. data/config/action_view_helpers/actionview/text_helper/word_wrap.yml +25 -15
  219. data/config/action_view_helpers/actionview/translation_helper/debug_missing_translation.yml +9 -6
  220. data/config/action_view_helpers/actionview/translation_helper/localize.yml +26 -16
  221. data/config/action_view_helpers/actionview/translation_helper/translate.yml +31 -19
  222. data/config/action_view_helpers/actionview/url_helper/button_to.yml +57 -33
  223. data/config/action_view_helpers/actionview/url_helper/button_to_generates_button_tag.yml +9 -6
  224. data/config/action_view_helpers/actionview/url_helper/current_page?.yml +25 -15
  225. data/config/action_view_helpers/actionview/url_helper/link_to.yml +76 -53
  226. data/config/action_view_helpers/actionview/url_helper/link_to_if.yml +54 -20
  227. data/config/action_view_helpers/actionview/url_helper/link_to_unless.yml +54 -20
  228. data/config/action_view_helpers/actionview/url_helper/link_to_unless_current.yml +49 -17
  229. data/config/action_view_helpers/actionview/url_helper/mail_to.yml +60 -34
  230. data/config/action_view_helpers/actionview/url_helper/phone_to.yml +40 -22
  231. data/config/action_view_helpers/actionview/url_helper/sms_to.yml +45 -25
  232. data/config/action_view_helpers/actionview/url_helper/url_for.yml +15 -9
  233. data/config/action_view_helpers/importmap-rails/importmap_tags_helper/javascript_import_module_tag.yml +32 -0
  234. data/config/action_view_helpers/importmap-rails/importmap_tags_helper/javascript_importmap_module_preload_tags.yml +39 -0
  235. data/config/action_view_helpers/importmap-rails/importmap_tags_helper/javascript_importmap_tags.yml +35 -0
  236. data/config/action_view_helpers/importmap-rails/importmap_tags_helper/javascript_inline_importmap_tag.yml +32 -0
  237. data/config/action_view_helpers/importmap-rails/importmap_tags_helper/javascript_module_preload_tag.yml +35 -0
  238. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_exempts_page_from_cache.yml +9 -6
  239. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_exempts_page_from_cache_tag.yml +12 -8
  240. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_exempts_page_from_preview.yml +9 -6
  241. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_exempts_page_from_preview_tag.yml +12 -8
  242. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_page_requires_reload.yml +9 -6
  243. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_page_requires_reload_tag.yml +12 -8
  244. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_refresh_method_tag.yml +17 -11
  245. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_refresh_scroll_tag.yml +17 -11
  246. data/config/action_view_helpers/turbo-rails/drive_helper/turbo_refreshes_with.yml +20 -12
  247. data/config/action_view_helpers/turbo-rails/frames_helper/turbo_frame_tag.yml +54 -33
  248. data/config/action_view_helpers/turbo-rails/includes_helper/turbo_include_tags.yml +9 -6
  249. data/config/action_view_helpers/turbo-rails/streams_action_helper/turbo_stream_action_tag.yml +32 -20
  250. data/config/action_view_helpers/turbo-rails/streams_action_helper/turbo_stream_refresh_tag.yml +18 -11
  251. data/config/action_view_helpers/turbo-rails/streams_helper/turbo_stream.yml +9 -6
  252. data/config/action_view_helpers/turbo-rails/streams_helper/turbo_stream_from.yml +28 -18
  253. data/config/html_elements.yml +597 -0
  254. data/config/options.yml +1 -0
  255. data/config/slots/components.yml +52 -0
  256. data/config/state/kind.yml +61 -0
  257. data/config/state/operators.yml +27 -0
  258. data/config/state/predicates.yml +39 -0
  259. data/config/state/transforms.yml +18 -0
  260. data/config/view_context_methods/actionview/base/annotate_rendered_view_with_filenames.yml +4 -4
  261. data/config/view_context_methods/actionview/base/assign.yml +4 -4
  262. data/config/view_context_methods/actionview/base/assigns.yml +4 -4
  263. data/config/view_context_methods/actionview/base/automatically_disable_submit_tag.yml +4 -4
  264. data/config/view_context_methods/actionview/base/compiled_method_container.yml +4 -4
  265. data/config/view_context_methods/actionview/base/config.yml +4 -4
  266. data/config/view_context_methods/actionview/base/default_form_builder.yml +4 -4
  267. data/config/view_context_methods/actionview/base/default_formats.yml +4 -4
  268. data/config/view_context_methods/actionview/base/field_error_proc.yml +4 -4
  269. data/config/view_context_methods/actionview/base/formats.yml +4 -4
  270. data/config/view_context_methods/actionview/base/in_rendering_context.yml +4 -4
  271. data/config/view_context_methods/actionview/base/locale.yml +4 -4
  272. data/config/view_context_methods/actionview/base/lookup_context.yml +4 -4
  273. data/config/view_context_methods/actionview/base/output_buffer.yml +4 -4
  274. data/config/view_context_methods/actionview/base/prefix_partial_path_with_controller_namespace.yml +4 -4
  275. data/config/view_context_methods/actionview/base/remove_hidden_field_autocomplete.yml +4 -4
  276. data/config/view_context_methods/actionview/base/streaming_completion_on_exception.yml +4 -4
  277. data/config/view_context_methods/actionview/base/view_flow.yml +4 -4
  278. data/config/view_context_methods/actionview/base/view_paths.yml +4 -4
  279. data/config/view_context_methods/actionview/base/view_renderer.yml +4 -4
  280. data/config/view_context_methods/actionview/template/local_assigns.yml +4 -4
  281. data/config/view_context_methods/activemodel/conversion/convert_to_model.yml +4 -4
  282. data/config/view_context_methods/activesupport/benchmarkable/benchmark.yml +4 -4
  283. data/config.yml +1249 -786
  284. data/exe/herb +2 -0
  285. data/ext/herb/error_helpers.c +379 -17
  286. data/ext/herb/error_helpers.h +1 -1
  287. data/ext/herb/extconf.rb +4 -2
  288. data/ext/herb/extension.c +137 -74
  289. data/ext/herb/extension_helpers.c +81 -35
  290. data/ext/herb/extension_helpers.h +4 -1
  291. data/ext/herb/nodes.c +859 -374
  292. data/ext/herb/nodes.h +3 -2
  293. data/ext/herb/parser_options_helpers.c +151 -0
  294. data/ext/herb/parser_options_helpers.h +14 -0
  295. data/herb.gemspec +3 -2
  296. data/lib/herb/action_view/helper_registry.rb +1358 -62
  297. data/lib/herb/analysis/partial_declaration.rb +140 -0
  298. data/lib/herb/analysis/partial_index.rb +189 -0
  299. data/lib/herb/analysis/partial_resolution.rb +161 -0
  300. data/lib/herb/analysis/partial_resolver.rb +144 -0
  301. data/lib/herb/analysis/project_index.rb +152 -0
  302. data/lib/herb/{action_view → analysis}/render_analyzer.rb +287 -77
  303. data/lib/herb/analysis/render_graph/ancestor_chain.rb +27 -0
  304. data/lib/herb/analysis/render_graph/builder.rb +376 -0
  305. data/lib/herb/analysis/render_graph/call_frame.rb +9 -0
  306. data/lib/herb/analysis/render_graph/call_site_location.rb +23 -0
  307. data/lib/herb/analysis/render_graph/inferred_signature.rb +21 -0
  308. data/lib/herb/analysis/render_graph/partial_call_site.rb +40 -0
  309. data/lib/herb/analysis/render_graph/partial_context.rb +46 -0
  310. data/lib/herb/analysis/render_graph/template_roots.rb +31 -0
  311. data/lib/herb/analysis/render_graph.rb +285 -0
  312. data/lib/herb/analysis/ruby_locals_index/local.rb +25 -0
  313. data/lib/herb/analysis/ruby_locals_index/named_reference.rb +22 -0
  314. data/lib/herb/analysis/ruby_locals_index/offset_table.rb +44 -0
  315. data/lib/herb/analysis/ruby_locals_index/reference_collector.rb +80 -0
  316. data/lib/herb/analysis/ruby_locals_index/state_locals.rb +49 -0
  317. data/lib/herb/analysis/ruby_locals_index.rb +165 -0
  318. data/lib/herb/analysis/ruby_reference.rb +67 -0
  319. data/lib/herb/analysis/template_dependencies/dependency_collector.rb +179 -0
  320. data/lib/herb/analysis/template_dependencies/node_dependency_collector.rb +309 -0
  321. data/lib/herb/analysis/template_dependencies.rb +562 -0
  322. data/lib/herb/ast/erb_render_node.rb +26 -94
  323. data/lib/herb/ast/helpers.rb +90 -10
  324. data/lib/herb/ast/herb_directive_node.rb +18 -0
  325. data/lib/herb/ast/node.rb +32 -2
  326. data/lib/herb/ast/nodes.rb +1368 -105
  327. data/lib/herb/backend.rb +73 -0
  328. data/lib/herb/bootstrap.rb +8 -8
  329. data/lib/herb/cli.rb +555 -32
  330. data/lib/herb/colors.rb +14 -0
  331. data/lib/herb/configuration.rb +71 -35
  332. data/lib/herb/defaults.yml +2 -1
  333. data/lib/herb/dev/assets.rb +57 -0
  334. data/lib/herb/dev/classifier.rb +84 -0
  335. data/lib/herb/dev/embedded.rb +27 -0
  336. data/lib/herb/dev/pipeline.rb +305 -0
  337. data/lib/herb/dev/protocol.rb +221 -0
  338. data/lib/herb/dev/runner.rb +180 -175
  339. data/lib/herb/dev/server.rb +89 -26
  340. data/lib/herb/dev/server_entry.rb +41 -6
  341. data/lib/herb/dev/watcher.rb +181 -0
  342. data/lib/herb/dev.rb +163 -0
  343. data/lib/herb/diagnostic/formatter.rb +88 -0
  344. data/lib/herb/diagnostic.rb +172 -0
  345. data/lib/herb/diff/operation.rb +36 -0
  346. data/lib/herb/diff/result.rb +62 -0
  347. data/lib/herb/diff.rb +10 -0
  348. data/lib/herb/engine/collector.rb +62 -0
  349. data/lib/herb/engine/compiler.rb +387 -113
  350. data/lib/herb/engine/component_tags/constant_resolver.rb +28 -0
  351. data/lib/herb/engine/component_tags/partial_resolver.rb +54 -0
  352. data/lib/herb/engine/component_tags/resolver.rb +32 -0
  353. data/lib/herb/engine/component_tags/visitor.rb +231 -0
  354. data/lib/herb/engine/css_inliner/inliner.rb +101 -0
  355. data/lib/herb/engine/css_inliner/visitor.rb +174 -0
  356. data/lib/herb/engine/errors.rb +122 -0
  357. data/lib/herb/engine/helpers.rb +82 -0
  358. data/lib/herb/engine/inline_render/inliner.rb +122 -0
  359. data/lib/herb/engine/inline_render/visitor.rb +267 -0
  360. data/lib/herb/engine/runtime/entry.rb +62 -0
  361. data/lib/herb/engine/runtime/error_page.rb +362 -0
  362. data/lib/herb/engine/runtime/html_safe_assertions.rb +217 -0
  363. data/lib/herb/engine/runtime/journal/summary.rb +227 -0
  364. data/lib/herb/engine/runtime/journal.rb +379 -0
  365. data/lib/herb/engine/runtime/middleware.rb +212 -0
  366. data/lib/herb/engine/runtime/observations.rb +73 -0
  367. data/lib/herb/engine/runtime/report.rb +204 -0
  368. data/lib/herb/engine/runtime/session.rb +437 -0
  369. data/lib/herb/engine/runtime.rb +21 -0
  370. data/lib/herb/engine/scoped_style/channel.rb +66 -0
  371. data/lib/herb/engine/scoped_style/collector.rb +74 -0
  372. data/lib/herb/engine/scoped_style/visitor.rb +533 -0
  373. data/lib/herb/engine/slots/annotation.rb +98 -0
  374. data/lib/herb/engine/slots/bindings.rb +61 -0
  375. data/lib/herb/engine/slots/callee.rb +118 -0
  376. data/lib/herb/engine/slots/component_definitions.rb +67 -0
  377. data/lib/herb/engine/slots/components/async.rb +24 -0
  378. data/lib/herb/engine/slots/components/base.rb +132 -0
  379. data/lib/herb/engine/slots/components/deferred.rb +81 -0
  380. data/lib/herb/engine/slots/components/fallback.rb +27 -0
  381. data/lib/herb/engine/slots/components/fragment.rb +99 -0
  382. data/lib/herb/engine/slots/components/lazy.rb +24 -0
  383. data/lib/herb/engine/slots/components.rb +52 -0
  384. data/lib/herb/engine/slots/dependencies/channel.rb +72 -0
  385. data/lib/herb/engine/slots/dependencies.rb +269 -0
  386. data/lib/herb/engine/slots/dynamics_compiler.rb +864 -0
  387. data/lib/herb/engine/slots/identifier.rb +35 -0
  388. data/lib/herb/engine/slots/manifest/channel.rb +74 -0
  389. data/lib/herb/engine/slots/manifest/collector.rb +75 -0
  390. data/lib/herb/engine/slots/markers.rb +152 -0
  391. data/lib/herb/engine/slots/render_bindings.rb +141 -0
  392. data/lib/herb/engine/slots/schema_compiler.rb +94 -0
  393. data/lib/herb/engine/slots/state_anchor.rb +94 -0
  394. data/lib/herb/engine/slots/state_compiler.rb +1325 -0
  395. data/lib/herb/engine/slots/state_directives.rb +854 -0
  396. data/lib/herb/engine/slots/state_kinds.rb +54 -0
  397. data/lib/herb/engine/slots/state_operators.rb +36 -0
  398. data/lib/herb/engine/slots/state_overrides.rb +79 -0
  399. data/lib/herb/engine/slots/state_predicates.rb +65 -0
  400. data/lib/herb/engine/slots/state_transforms.rb +42 -0
  401. data/lib/herb/engine/slots/statics.rb +185 -0
  402. data/lib/herb/engine/slots/subtree.rb +82 -0
  403. data/lib/herb/engine/slots/subtree_compiler.rb +255 -0
  404. data/lib/herb/engine/slots/types.rb +42 -0
  405. data/lib/herb/engine/slots/visitor.rb +2267 -0
  406. data/lib/herb/engine/validators/accessibility_validator.rb +3 -3
  407. data/lib/herb/engine/validators/base.rb +36 -0
  408. data/lib/herb/engine/validators/generator_template_validator.rb +39 -0
  409. data/lib/herb/engine/validators/nesting_validator.rb +3 -3
  410. data/lib/herb/engine/validators/render_validator.rb +13 -33
  411. data/lib/herb/engine/validators/security_validator.rb +3 -17
  412. data/lib/herb/engine/validators.rb +43 -0
  413. data/lib/herb/engine/visitors/auto_close_omitted_tags_visitor.rb +48 -0
  414. data/lib/herb/engine/visitors/content_for_visitor.rb +127 -0
  415. data/lib/herb/engine/{debug_visitor.rb → visitors/debug_visitor.rb} +122 -94
  416. data/lib/herb/engine/visitors/html_safe_assertions_visitor.rb +281 -0
  417. data/lib/herb/engine/visitors/instrumentation_visitor.rb +290 -0
  418. data/lib/herb/engine/visitors/optimize_visitor.rb +728 -0
  419. data/lib/herb/engine/visitors/remove_comments_visitor.rb +106 -0
  420. data/lib/herb/engine/visitors/source_attribution_visitor.rb +177 -0
  421. data/lib/herb/engine.rb +218 -259
  422. data/lib/herb/ensure_installed.rb +43 -0
  423. data/lib/herb/errors.rb +681 -153
  424. data/lib/herb/fingerprint.rb +82 -0
  425. data/lib/herb/html/elements.rb +18 -0
  426. data/lib/herb/html/util.rb +21 -2
  427. data/lib/herb/lex_result.rb +0 -10
  428. data/lib/herb/locate.rb +163 -0
  429. data/lib/herb/location.rb +20 -0
  430. data/lib/herb/parse_result.rb +17 -11
  431. data/lib/herb/parser_options.rb +69 -40
  432. data/lib/herb/position.rb +19 -0
  433. data/lib/herb/prism_inspect.rb +1 -4
  434. data/lib/herb/project.rb +39 -23
  435. data/lib/herb/range.rb +5 -0
  436. data/lib/herb/result.rb +2 -2
  437. data/lib/herb/ruby_program.rb +110 -0
  438. data/lib/herb/source_path.rb +232 -0
  439. data/lib/herb/token.rb +14 -3
  440. data/lib/herb/version.rb +1 -1
  441. data/lib/herb/visitor/context/origin.rb +52 -0
  442. data/lib/herb/visitor/context/replacements.rb +59 -0
  443. data/lib/herb/visitor/context.rb +169 -0
  444. data/lib/herb/visitor/context_aware.rb +45 -0
  445. data/lib/herb/visitor/diagnostics.rb +148 -0
  446. data/lib/herb/visitor/experimental.rb +33 -0
  447. data/lib/herb/visitor/parser_option_requirements.rb +151 -0
  448. data/lib/herb/visitor/stack.rb +192 -0
  449. data/lib/herb/visitor.rb +47 -1
  450. data/lib/herb/warnings.rb +25 -0
  451. data/lib/herb.rb +74 -63
  452. data/sig/herb/action_view/helper_registry.rbs +82 -10
  453. data/sig/herb/analysis/partial_declaration.rbs +80 -0
  454. data/sig/herb/analysis/partial_index.rbs +66 -0
  455. data/sig/herb/analysis/partial_resolution.rbs +57 -0
  456. data/sig/herb/analysis/partial_resolver.rbs +76 -0
  457. data/sig/herb/analysis/project_index.rbs +57 -0
  458. data/sig/herb/{action_view → analysis}/render_analyzer.rbs +25 -4
  459. data/sig/herb/analysis/render_graph/ancestor_chain.rbs +22 -0
  460. data/sig/herb/analysis/render_graph/builder.rbs +180 -0
  461. data/sig/herb/analysis/render_graph/call_frame.rbs +26 -0
  462. data/sig/herb/analysis/render_graph/call_site_location.rbs +28 -0
  463. data/sig/herb/analysis/render_graph/inferred_signature.rbs +27 -0
  464. data/sig/herb/analysis/render_graph/partial_call_site.rbs +36 -0
  465. data/sig/herb/analysis/render_graph/partial_context.rbs +31 -0
  466. data/sig/herb/analysis/render_graph/template_roots.rbs +32 -0
  467. data/sig/herb/analysis/render_graph.rbs +83 -0
  468. data/sig/herb/analysis/ruby_locals_index/local.rbs +21 -0
  469. data/sig/herb/analysis/ruby_locals_index/named_reference.rbs +20 -0
  470. data/sig/herb/analysis/ruby_locals_index/offset_table.rbs +26 -0
  471. data/sig/herb/analysis/ruby_locals_index/reference_collector.rbs +40 -0
  472. data/sig/herb/analysis/ruby_locals_index/state_locals.rbs +19 -0
  473. data/sig/herb/analysis/ruby_locals_index.rbs +62 -0
  474. data/sig/herb/analysis/ruby_reference.rbs +28 -0
  475. data/sig/herb/analysis/template_dependencies/dependency_collector.rbs +52 -0
  476. data/sig/herb/analysis/template_dependencies/node_dependency_collector.rbs +81 -0
  477. data/sig/herb/analysis/template_dependencies.rbs +125 -0
  478. data/sig/herb/ast/erb_render_node.rbs +10 -2
  479. data/sig/herb/ast/helpers.rbs +31 -1
  480. data/sig/herb/ast/herb_directive_node.rbs +13 -0
  481. data/sig/herb/ast/node.rbs +9 -0
  482. data/sig/herb/ast/nodes.rbs +668 -89
  483. data/sig/herb/backend.rbs +52 -0
  484. data/sig/herb/bootstrap.rbs +2 -0
  485. data/sig/herb/colors.rbs +3 -0
  486. data/sig/herb/configuration.rbs +27 -3
  487. data/sig/herb/dev/assets.rbs +27 -0
  488. data/sig/herb/dev/classifier.rbs +57 -0
  489. data/sig/herb/dev/embedded.rbs +21 -0
  490. data/sig/herb/dev/pipeline.rbs +118 -0
  491. data/sig/herb/dev/protocol.rbs +78 -0
  492. data/sig/herb/dev/runner.rbs +64 -23
  493. data/sig/herb/dev/server.rbs +49 -22
  494. data/sig/herb/dev/server_entry.rbs +46 -21
  495. data/sig/herb/dev/watcher.rbs +84 -0
  496. data/sig/herb/dev.rbs +58 -0
  497. data/sig/herb/diagnostic/formatter.rbs +28 -0
  498. data/sig/herb/diagnostic.rbs +88 -0
  499. data/sig/herb/diff/operation.rbs +36 -0
  500. data/sig/herb/diff/result.rbs +36 -0
  501. data/sig/herb/diff.rbs +6 -0
  502. data/sig/herb/engine/collector.rbs +48 -0
  503. data/sig/herb/engine/compiler.rbs +74 -24
  504. data/sig/herb/engine/component_tags/constant_resolver.rbs +19 -0
  505. data/sig/herb/engine/component_tags/partial_resolver.rbs +30 -0
  506. data/sig/herb/engine/component_tags/resolver.rbs +25 -0
  507. data/sig/herb/engine/component_tags/visitor.rbs +69 -0
  508. data/sig/herb/engine/css_inliner/inliner.rbs +65 -0
  509. data/sig/herb/engine/css_inliner/visitor.rbs +87 -0
  510. data/sig/herb/engine/errors.rbs +68 -0
  511. data/sig/herb/engine/helpers.rbs +28 -0
  512. data/sig/herb/engine/inline_render/inliner.rbs +39 -0
  513. data/sig/herb/engine/inline_render/visitor.rbs +104 -0
  514. data/sig/herb/engine/runtime/entry.rbs +43 -0
  515. data/sig/herb/engine/runtime/error_page.rbs +115 -0
  516. data/sig/herb/engine/runtime/html_safe_assertions.rbs +99 -0
  517. data/sig/herb/engine/runtime/journal/summary.rbs +172 -0
  518. data/sig/herb/engine/runtime/journal.rbs +126 -0
  519. data/sig/herb/engine/runtime/middleware.rbs +96 -0
  520. data/sig/herb/engine/runtime/observations.rbs +38 -0
  521. data/sig/herb/engine/runtime/report.rbs +97 -0
  522. data/sig/herb/engine/runtime/session.rbs +233 -0
  523. data/sig/herb/engine/runtime.rbs +14 -0
  524. data/sig/herb/engine/scoped_style/channel.rbs +45 -0
  525. data/sig/herb/engine/scoped_style/collector.rbs +45 -0
  526. data/sig/herb/engine/scoped_style/visitor.rbs +220 -0
  527. data/sig/herb/engine/slots/annotation.rbs +72 -0
  528. data/sig/herb/engine/slots/bindings.rbs +34 -0
  529. data/sig/herb/engine/slots/callee.rbs +62 -0
  530. data/sig/herb/engine/slots/component_definitions.rbs +19 -0
  531. data/sig/herb/engine/slots/components/async.rbs +20 -0
  532. data/sig/herb/engine/slots/components/base.rbs +60 -0
  533. data/sig/herb/engine/slots/components/deferred.rbs +29 -0
  534. data/sig/herb/engine/slots/components/fallback.rbs +21 -0
  535. data/sig/herb/engine/slots/components/fragment.rbs +34 -0
  536. data/sig/herb/engine/slots/components/lazy.rbs +20 -0
  537. data/sig/herb/engine/slots/components.rbs +30 -0
  538. data/sig/herb/engine/slots/dependencies/channel.rbs +51 -0
  539. data/sig/herb/engine/slots/dependencies.rbs +94 -0
  540. data/sig/herb/engine/slots/dynamics_compiler.rbs +352 -0
  541. data/sig/herb/engine/slots/identifier.rbs +19 -0
  542. data/sig/herb/engine/slots/manifest/channel.rbs +49 -0
  543. data/sig/herb/engine/slots/manifest/collector.rbs +45 -0
  544. data/sig/herb/engine/slots/markers.rbs +99 -0
  545. data/sig/herb/engine/slots/render_bindings.rbs +69 -0
  546. data/sig/herb/engine/slots/schema_compiler.rbs +62 -0
  547. data/sig/herb/engine/slots/state_anchor.rbs +41 -0
  548. data/sig/herb/engine/slots/state_compiler.rbs +258 -0
  549. data/sig/herb/engine/slots/state_directives.rbs +278 -0
  550. data/sig/herb/engine/slots/state_kinds.rbs +24 -0
  551. data/sig/herb/engine/slots/state_operators.rbs +19 -0
  552. data/sig/herb/engine/slots/state_overrides.rbs +33 -0
  553. data/sig/herb/engine/slots/state_predicates.rbs +16 -0
  554. data/sig/herb/engine/slots/state_transforms.rbs +15 -0
  555. data/sig/herb/engine/slots/statics.rbs +67 -0
  556. data/sig/herb/engine/slots/subtree.rbs +49 -0
  557. data/sig/herb/engine/slots/subtree_compiler.rbs +130 -0
  558. data/sig/herb/engine/slots/types.rbs +46 -0
  559. data/sig/herb/engine/slots/visitor.rbs +603 -0
  560. data/sig/herb/engine/validators/accessibility_validator.rbs +1 -1
  561. data/sig/herb/engine/validators/base.rbs +28 -0
  562. data/sig/herb/engine/validators/generator_template_validator.rbs +19 -0
  563. data/sig/herb/engine/validators/nesting_validator.rbs +1 -1
  564. data/sig/herb/engine/validators/render_validator.rbs +2 -6
  565. data/sig/herb/engine/validators/security_validator.rbs +1 -3
  566. data/sig/herb/engine/validators.rbs +21 -0
  567. data/sig/herb/engine/visitors/auto_close_omitted_tags_visitor.rbs +26 -0
  568. data/sig/herb/engine/visitors/content_for_visitor.rbs +52 -0
  569. data/sig/herb/engine/{debug_visitor.rbs → visitors/debug_visitor.rbs} +26 -15
  570. data/sig/herb/engine/visitors/html_safe_assertions_visitor.rbs +100 -0
  571. data/sig/herb/engine/visitors/instrumentation_visitor.rbs +111 -0
  572. data/sig/herb/engine/visitors/optimize_visitor.rbs +246 -0
  573. data/sig/herb/engine/visitors/remove_comments_visitor.rbs +66 -0
  574. data/sig/herb/engine/visitors/source_attribution_visitor.rbs +90 -0
  575. data/sig/herb/engine.rbs +47 -41
  576. data/sig/herb/ensure_installed.rbs +9 -0
  577. data/sig/herb/errors.rbs +311 -0
  578. data/sig/herb/fingerprint.rbs +37 -0
  579. data/sig/herb/html/elements.rbs +19 -0
  580. data/sig/herb/html/util.rbs +15 -1
  581. data/sig/herb/lex_result.rbs +0 -6
  582. data/sig/herb/locate.rbs +89 -0
  583. data/sig/herb/location.rbs +12 -0
  584. data/sig/herb/parse_result.rbs +10 -8
  585. data/sig/herb/parser_options.rbs +53 -19
  586. data/sig/herb/position.rbs +11 -0
  587. data/sig/herb/range.rbs +3 -0
  588. data/sig/herb/ruby_program.rbs +51 -0
  589. data/sig/herb/source_path.rbs +132 -0
  590. data/sig/herb/token.rbs +5 -2
  591. data/sig/herb/visitor/context/origin.rbs +51 -0
  592. data/sig/herb/visitor/context/replacements.rbs +39 -0
  593. data/sig/herb/visitor/context.rbs +83 -0
  594. data/sig/herb/visitor/context_aware.rbs +32 -0
  595. data/sig/herb/visitor/diagnostics.rbs +82 -0
  596. data/sig/herb/visitor/experimental.rbs +23 -0
  597. data/sig/herb/visitor/parser_option_requirements.rbs +76 -0
  598. data/sig/herb/visitor/stack.rbs +91 -0
  599. data/sig/herb/visitor.rbs +22 -0
  600. data/sig/herb/warnings.rbs +10 -0
  601. data/sig/herb.rbs +27 -10
  602. data/sig/manifest.yaml +13 -0
  603. data/sig/serialized_ast_errors.rbs +62 -1
  604. data/sig/serialized_ast_nodes.rbs +59 -2
  605. data/src/analyze/action_view/attribute_extraction_helpers.c +73 -36
  606. data/src/analyze/action_view/generated_handlers.c +37 -1
  607. data/src/analyze/action_view/generated_handlers.h +2 -1
  608. data/src/analyze/action_view/helper_registry.c +1353 -58
  609. data/src/analyze/action_view/registry.c +10 -17
  610. data/src/analyze/action_view/stylesheet_link_tag.c +40 -0
  611. data/src/analyze/action_view/tag_helper_node_builders.c +63 -3
  612. data/src/analyze/action_view/tag_helpers.c +502 -111
  613. data/src/analyze/analyze.c +306 -89
  614. data/src/analyze/analyze_helpers.c +502 -237
  615. data/src/analyze/analyzed_ruby.c +2 -0
  616. data/src/analyze/builders.c +80 -51
  617. data/src/analyze/conditional_elements.c +13 -8
  618. data/src/analyze/conditional_open_tags.c +11 -5
  619. data/src/analyze/control_type.c +36 -13
  620. data/src/analyze/herb_directives.c +701 -0
  621. data/src/analyze/invalid_structures.c +11 -7
  622. data/src/analyze/iteration_nodes.c +306 -0
  623. data/src/analyze/missing_end.c +116 -32
  624. data/src/analyze/parse_errors.c +8 -3
  625. data/src/analyze/postfix_conditionals.c +98 -63
  626. data/src/analyze/prism_annotate.c +45 -13
  627. data/src/analyze/render_nodes.c +348 -123
  628. data/src/analyze/strict_locals.c +30 -26
  629. data/src/analyze/ternary_conditionals.c +95 -70
  630. data/src/analyze/transform.c +43 -1
  631. data/src/ast/ast_node.c +3 -13
  632. data/src/ast/ast_nodes.c +914 -113
  633. data/src/ast/ast_pretty_print.c +166 -3
  634. data/src/diff/herb_diff.c +8 -0
  635. data/src/diff/herb_diff_attributes.c +1 -0
  636. data/src/diff/herb_diff_children.c +4 -3
  637. data/src/diff/herb_diff_helpers.c +7 -1
  638. data/src/diff/herb_diff_nodes.c +186 -71
  639. data/src/diff/herb_hash_tree.c +99 -1
  640. data/src/errors.c +1124 -105
  641. data/src/extract.c +227 -144
  642. data/src/herb.c +43 -8
  643. data/src/include/analyze/action_view/attribute_extraction_helpers.h +1 -1
  644. data/src/include/analyze/action_view/helper_registry.h +28 -4
  645. data/src/include/analyze/action_view/tag_helper_handler.h +1 -1
  646. data/src/include/analyze/action_view/tag_helper_node_builders.h +7 -0
  647. data/src/include/analyze/analyze.h +11 -22
  648. data/src/include/analyze/analyzed_ruby.h +25 -0
  649. data/src/include/analyze/builders.h +16 -0
  650. data/src/include/analyze/helpers.h +25 -24
  651. data/src/include/analyze/herb_directive_kinds.h +41 -0
  652. data/src/include/analyze/herb_directives.h +14 -0
  653. data/src/include/analyze/iteration_nodes.h +11 -0
  654. data/src/include/analyze/prism_annotate.h +3 -3
  655. data/src/include/analyze/ternary_conditionals.h +2 -0
  656. data/src/include/ast/ast_node.h +1 -5
  657. data/src/include/ast/ast_nodes.h +78 -3
  658. data/src/include/ast/ast_pretty_print.h +1 -1
  659. data/src/include/diff/herb_diff.h +11 -0
  660. data/src/include/errors.h +163 -38
  661. data/src/include/extract.h +11 -0
  662. data/src/include/herb.h +6 -0
  663. data/src/include/lexer/erb_openers.h +29 -0
  664. data/src/include/lexer/lexer.h +1 -0
  665. data/src/include/lexer/lexer_peek_helpers.h +8 -1
  666. data/src/include/lexer/lexer_struct.h +4 -0
  667. data/src/include/lexer/token.h +5 -0
  668. data/src/include/lexer/token_struct.h +8 -6
  669. data/src/include/lib/hb_allocator.h +2 -1
  670. data/src/include/lib/hb_array.h +2 -0
  671. data/src/include/lib/hb_clock.h +15 -0
  672. data/src/include/lib/hb_foreach.h +1 -1
  673. data/src/include/lib/hb_string.h +7 -0
  674. data/src/include/parser/dot_notation.h +1 -1
  675. data/src/include/parser/foreign_content_elements.h +32 -0
  676. data/src/include/parser/parser.h +40 -26
  677. data/src/include/parser/parser_helpers.h +13 -11
  678. data/src/include/parser/parser_options.h +41 -0
  679. data/src/include/prism/herb_prism_node.h +6 -1
  680. data/src/include/prism/ruby_parser.h +6 -0
  681. data/src/include/util/html_elements.h +85 -0
  682. data/src/include/util/html_util.h +1 -0
  683. data/src/include/version.h +1 -1
  684. data/src/lexer/lexer_peek_helpers.c +15 -1
  685. data/src/lexer/token.c +89 -1
  686. data/src/lexer.c +228 -12
  687. data/src/lib/hb_allocator.c +2 -0
  688. data/src/lib/hb_array.c +26 -0
  689. data/src/lib/hb_string.c +26 -0
  690. data/src/main.c +1 -1
  691. data/src/parser/dot_notation.c +4 -3
  692. data/src/parser/match_tags.c +66 -1
  693. data/src/parser/parser_helpers.c +69 -37
  694. data/src/parser.c +658 -385
  695. data/src/parser_options_defaults.c +30 -0
  696. data/src/prism/prism_helpers.c +94 -0
  697. data/src/prism/ruby_parser.c +49 -0
  698. data/src/util/html_util.c +11 -58
  699. data/src/visitor.c +77 -1
  700. data/templates/ext/herb/error_helpers.c.erb +1 -1
  701. data/templates/ext/herb/nodes.c.erb +19 -14
  702. data/templates/ext/herb/nodes.h.erb +2 -1
  703. data/templates/ext/herb/parser_options_helpers.c.erb +69 -0
  704. data/templates/ext/herb/parser_options_helpers.h.erb +11 -0
  705. data/templates/java/nodes.c.erb +21 -17
  706. data/templates/java/nodes.h.erb +3 -2
  707. data/templates/java/org/herb/ParserOptions.java.erb +24 -0
  708. data/templates/java/org/herb/ast/HelperRegistry.java.erb +67 -31
  709. data/templates/java/org/herb/ast/HelperType.java.erb +29 -0
  710. data/templates/java/org/herb/ast/Nodes.java.erb +11 -7
  711. data/templates/java/parser_options_helpers.c.erb +45 -0
  712. data/templates/java/parser_options_helpers.h.erb +10 -0
  713. data/templates/javascript/packages/client/src/state-kinds.ts.erb +49 -0
  714. data/templates/javascript/packages/client/src/state-operators.ts.erb +15 -0
  715. data/templates/javascript/packages/client/src/state-predicates.ts.erb +27 -0
  716. data/templates/javascript/packages/client/src/state-transforms.ts.erb +19 -0
  717. data/templates/javascript/packages/core/src/action-view-helpers.ts.erb +47 -0
  718. data/templates/javascript/packages/core/src/built-in-components.ts.erb +67 -0
  719. data/templates/javascript/packages/core/src/errors.ts.erb +9 -3
  720. data/templates/javascript/packages/core/src/foreign-content-elements.ts.erb +18 -0
  721. data/templates/javascript/packages/core/src/html-constants.ts.erb +32 -0
  722. data/templates/javascript/packages/core/src/html-elements.ts.erb +41 -0
  723. data/templates/javascript/packages/core/src/node-type-guards.ts.erb +11 -7
  724. data/templates/javascript/packages/core/src/nodes.ts.erb +103 -20
  725. data/templates/javascript/packages/core/src/parser-options.ts.erb +41 -0
  726. data/templates/javascript/packages/core/src/visitor.ts.erb +1 -1
  727. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +3 -3
  728. data/templates/javascript/packages/node/extension/nodes.cpp.erb +18 -13
  729. data/templates/javascript/packages/node/extension/nodes.h.erb +3 -3
  730. data/templates/javascript/packages/node/extension/parser_options_helpers.cpp.erb +41 -0
  731. data/templates/javascript/packages/node/extension/parser_options_helpers.h.erb +12 -0
  732. data/templates/lib/herb/action_view/helper_registry.rb.erb +77 -3
  733. data/templates/lib/herb/ast/nodes.rb.erb +16 -6
  734. data/templates/lib/herb/engine/slots/component_definitions.rb.erb +38 -0
  735. data/templates/lib/herb/engine/slots/state_kinds.rb.erb +37 -0
  736. data/templates/lib/herb/engine/slots/state_operators.rb.erb +26 -0
  737. data/templates/lib/herb/engine/slots/state_predicates.rb.erb +39 -0
  738. data/templates/lib/herb/engine/slots/state_transforms.rb.erb +27 -0
  739. data/templates/lib/herb/errors.rb.erb +34 -6
  740. data/templates/lib/herb/html/elements.rb.erb +12 -0
  741. data/templates/lib/herb/parser_options.rb.erb +32 -0
  742. data/templates/lib/herb/visitor.rb.erb +7 -1
  743. data/templates/rust/herb-printer/src/printer_visitor.rs.erb +14 -0
  744. data/templates/rust/src/action_view_helpers.rs.erb +38 -0
  745. data/templates/rust/src/ast/nodes.rs.erb +21 -31
  746. data/templates/rust/src/errors.rs.erb +1 -1
  747. data/templates/rust/src/nodes.rs.erb +58 -26
  748. data/templates/rust/src/parser_options.rs.erb +16 -0
  749. data/templates/rust/src/prism/generated_deserialize.rs.erb +109 -0
  750. data/templates/rust/src/prism/generated_prettyprint.rs.erb +153 -0
  751. data/templates/rust/src/union_types.rs.erb +7 -1
  752. data/templates/rust/src/visitor.rs.erb +1 -1
  753. data/templates/src/analyze/action_view/helper_registry.c.erb +22 -0
  754. data/templates/src/analyze/missing_end.c.erb +13 -5
  755. data/templates/src/ast/ast_nodes.c.erb +41 -2
  756. data/templates/src/diff/herb_diff_nodes.c.erb +44 -8
  757. data/templates/src/errors.c.erb +8 -3
  758. data/templates/src/include/analyze/action_view/helper_registry.h.erb +19 -0
  759. data/templates/src/include/analyze/herb_directive_kinds.h.erb +25 -0
  760. data/templates/src/include/errors.h.erb +3 -1
  761. data/templates/src/include/parser/foreign_content_elements.h.erb +23 -0
  762. data/templates/src/include/parser/parser_options.h.erb +21 -0
  763. data/templates/src/include/util/html_elements.h.erb +28 -0
  764. data/templates/src/parser_options_defaults.c.erb +9 -0
  765. data/templates/template.rb +564 -9
  766. data/templates/wasm/error_helpers.cpp.erb +4 -4
  767. data/templates/wasm/nodes.cpp.erb +17 -27
  768. data/templates/wasm/nodes.h.erb +3 -3
  769. data/templates/wasm/parser_options_helpers.cpp.erb +25 -0
  770. data/templates/wasm/parser_options_helpers.h.erb +12 -0
  771. metadata +309 -30
  772. data/config/action_view_helpers/actionview/form_helper/button.yml +0 -38
  773. data/config/action_view_helpers/actionview/form_helper/submit.yml +0 -35
  774. data/lib/herb/ast.rb +0 -7
  775. data/lib/herb/diff_operation.rb +0 -34
  776. data/lib/herb/diff_result.rb +0 -59
  777. data/lib/herb/engine/error_formatter.rb +0 -432
  778. data/lib/herb/engine/parser_error_overlay.rb +0 -771
  779. data/lib/herb/engine/validation_error_overlay.rb +0 -182
  780. data/lib/herb/engine/validation_errors.rb +0 -65
  781. data/lib/herb/engine/validator.rb +0 -80
  782. data/sig/herb/ast.rbs +0 -6
  783. data/sig/herb/diff_operation.rbs +0 -34
  784. data/sig/herb/diff_result.rbs +0 -34
  785. data/sig/herb/engine/debug.rbs +0 -38
  786. data/sig/herb/engine/error_formatter.rbs +0 -47
  787. data/sig/herb/engine/parser_error_overlay.rbs +0 -41
  788. data/sig/herb/engine/validation_error_overlay.rbs +0 -35
  789. data/sig/herb/engine/validation_errors.rbs +0 -45
  790. data/sig/herb/engine/validator.rbs +0 -41
  791. data/sig/herb_c_extension.rbs +0 -10
  792. data/sig/vendor/did_you_mean.rbs +0 -6
  793. data/sig/vendor/parallel.rbs +0 -4
data/src/parser.c CHANGED
@@ -23,10 +23,10 @@
23
23
  #include <strings.h>
24
24
 
25
25
  #define MAX_CONSECUTIVE_ERRORS 10
26
-
27
- static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T* errors);
28
- static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_array_T* errors);
29
- static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser);
26
+ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T** errors);
27
+ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_array_T** errors);
28
+ static bool parser_element_has_foreign_content(parser_T* parser, hb_string_T tag_name);
29
+ static AST_NODE_T* parser_parse_erb_tag(parser_T* parser);
30
30
  static void parser_handle_whitespace(parser_T* parser, token_T* whitespace_token, hb_array_T* children);
31
31
  static void parser_consume_whitespace(parser_T* parser, hb_array_T* children);
32
32
  static void parser_skip_erb_content(lexer_T* lexer);
@@ -35,21 +35,6 @@ static bool parser_lookahead_erb_is_control_flow(parser_T* parser);
35
35
  static void parser_handle_erb_in_open_tag(parser_T* parser, hb_array_T* children);
36
36
  static void parser_handle_whitespace_in_open_tag(parser_T* parser, hb_array_T* children);
37
37
 
38
- const parser_options_T HERB_DEFAULT_PARSER_OPTIONS = { .track_whitespace = false,
39
- .analyze = true,
40
- .strict = true,
41
- .action_view_helpers = false,
42
- .transform_conditionals = false,
43
- .render_nodes = false,
44
- .strict_locals = false,
45
- .prism_nodes_deep = false,
46
- .prism_nodes = false,
47
- .prism_program = false,
48
- .dot_notation_tags = false,
49
- .html = true,
50
- .start_line = 0,
51
- .start_column = 0 };
52
-
53
38
  size_t parser_sizeof(void) {
54
39
  return sizeof(struct PARSER_STRUCT);
55
40
  }
@@ -60,25 +45,32 @@ void herb_parser_init(parser_T* parser, lexer_T* lexer, parser_options_T options
60
45
  parser->current_token = lexer_next_token(lexer);
61
46
  parser->open_tags_stack = hb_array_init(16, parser->allocator);
62
47
  parser->state = PARSER_STATE_DATA;
63
- parser->foreign_content_type = FOREIGN_CONTENT_UNKNOWN;
48
+ parser->foreign_content_kind = FOREIGN_CONTENT_NONE;
49
+ parser->foreign_content_tag_name = HB_STRING_NULL;
50
+ parser->svg_depth = 0;
51
+ parser->xml_document = false;
64
52
  parser->options = options;
65
53
  parser->consecutive_error_count = 0;
66
54
  parser->in_recovery_mode = false;
55
+
56
+ for (size_t index = 0; index < HERB_MAX_FOREIGN_CONTENT_ELEMENTS; index++) {
57
+ parser->foreign_content_absent_from[index] = UINT32_MAX;
58
+ }
67
59
  }
68
60
 
69
61
  static AST_CDATA_NODE_T* parser_parse_cdata(parser_T* parser) {
70
- hb_array_T* errors = hb_array_init(8, parser->allocator);
62
+ hb_array_T* errors = NULL;
71
63
  hb_array_T* children = hb_array_init(8, parser->allocator);
72
64
  hb_buffer_T content;
73
65
  hb_buffer_init(&content, 128, parser->allocator);
74
66
 
75
- token_T* tag_opening = parser_consume_expected(parser, TOKEN_CDATA_START, errors);
67
+ token_T* tag_opening = parser_consume_expected(parser, TOKEN_CDATA_START, &errors);
76
68
  position_T start = parser->current_token->location.start;
77
69
 
78
70
  while (token_is_none_of(parser, TOKEN_CDATA_END, TOKEN_EOF)) {
79
71
  if (token_is(parser, TOKEN_ERB_START)) {
80
72
  parser_append_literal_node_from_buffer(parser, &content, children, start);
81
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
73
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
82
74
  hb_array_append(children, erb_node);
83
75
  start = parser->current_token->location.start;
84
76
  continue;
@@ -90,7 +82,7 @@ static AST_CDATA_NODE_T* parser_parse_cdata(parser_T* parser) {
90
82
  }
91
83
 
92
84
  parser_append_literal_node_from_buffer(parser, &content, children, start);
93
- token_T* tag_closing = parser_consume_expected(parser, TOKEN_CDATA_END, errors);
85
+ token_T* tag_closing = parser_consume_expected(parser, TOKEN_CDATA_END, &errors);
94
86
 
95
87
  AST_CDATA_NODE_T* cdata = ast_cdata_node_init(
96
88
  tag_opening,
@@ -103,26 +95,59 @@ static AST_CDATA_NODE_T* parser_parse_cdata(parser_T* parser) {
103
95
  );
104
96
 
105
97
  hb_buffer_free(&content);
106
- token_free(tag_opening, parser->allocator);
107
- token_free(tag_closing, parser->allocator);
108
98
 
109
99
  return cdata;
110
100
  }
111
101
 
102
+ // <!-- a <!-->
103
+ static token_T* parser_parse_html_comment_abrupt_end(
104
+ parser_T* parser,
105
+ hb_buffer_T* comment,
106
+ hb_array_T* children,
107
+ position_T start
108
+ ) {
109
+ token_T* nested_start = parser_advance(parser);
110
+ token_T* tag_end = parser_advance(parser);
111
+
112
+ position_T dashes_start = nested_start->location.start;
113
+ dashes_start.column += 2;
114
+
115
+ hb_buffer_append_string(comment, hb_string("<!"));
116
+
117
+ hb_string_T content = { .data = comment->value, .length = (uint32_t) comment->length };
118
+ hb_array_append(children, ast_literal_node_init(content, start, dashes_start, NULL, parser->allocator));
119
+ hb_buffer_clear(comment);
120
+
121
+ token_T* comment_end = hb_allocator_alloc(parser->allocator, sizeof(token_T));
122
+
123
+ comment_end->type = TOKEN_HTML_COMMENT_END;
124
+ comment_end->value = hb_string_from_data(hb_allocator_strndup(parser->allocator, "-->", 3), 3);
125
+ comment_end->owns_value = true;
126
+ comment_end->location = (location_T) { .start = dashes_start, .end = tag_end->location.end };
127
+ comment_end->range = (range_T) { .from = nested_start->range.from + 2, .to = tag_end->range.to };
128
+
129
+ token_free(nested_start, parser->allocator);
130
+ token_free(tag_end, parser->allocator);
131
+
132
+ return comment_end;
133
+ }
134
+
112
135
  static AST_HTML_COMMENT_NODE_T* parser_parse_html_comment(parser_T* parser) {
113
- hb_array_T* errors = hb_array_init(8, parser->allocator);
136
+ hb_array_T* errors = NULL;
114
137
  hb_array_T* children = hb_array_init(8, parser->allocator);
115
- token_T* comment_start = parser_consume_expected(parser, TOKEN_HTML_COMMENT_START, errors);
138
+ token_T* comment_start = parser_consume_expected(parser, TOKEN_HTML_COMMENT_START, &errors);
116
139
  position_T start = parser->current_token->location.start;
117
140
 
118
141
  hb_buffer_T comment;
119
142
  hb_buffer_init(&comment, 512, parser->allocator);
120
143
 
144
+ token_T* comment_end = NULL;
145
+
121
146
  while (token_is_none_of(parser, TOKEN_HTML_COMMENT_END, TOKEN_HTML_COMMENT_INVALID_END, TOKEN_EOF)) {
122
147
  if (token_is(parser, TOKEN_ERB_START)) {
123
148
  parser_append_literal_node_from_buffer(parser, &comment, children, start);
124
149
 
125
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
150
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
126
151
  hb_array_append(children, erb_node);
127
152
 
128
153
  start = parser->current_token->location.start;
@@ -130,6 +155,33 @@ static AST_HTML_COMMENT_NODE_T* parser_parse_html_comment(parser_T* parser) {
130
155
  continue;
131
156
  }
132
157
 
158
+ if (token_is(parser, TOKEN_HTML_COMMENT_START)) {
159
+ lexer_T lexer_copy = *parser->lexer;
160
+ token_T* next_token = lexer_next_token(&lexer_copy);
161
+ token_type_T next_type = next_token ? next_token->type : TOKEN_EOF;
162
+
163
+ if (next_token) { token_free(next_token, parser->allocator); }
164
+
165
+ // <!--[if !mso]><!--><meta><!--<![endif]-->
166
+ if (next_type == TOKEN_HTML_TAG_END) {
167
+ comment_end = parser_parse_html_comment_abrupt_end(parser, &comment, children, start);
168
+ break;
169
+ }
170
+
171
+ // <!-- outer <!-- inner -->
172
+ if (next_type != TOKEN_EOF && parser->options.strict) {
173
+ append_nested_comment_error(
174
+ comment_start,
175
+ parser->current_token,
176
+ parser->current_token->location.start,
177
+ parser->current_token->location.end,
178
+ parser->allocator,
179
+ &errors,
180
+ &parser->options
181
+ );
182
+ }
183
+ }
184
+
133
185
  token_T* token = parser_advance(parser);
134
186
  hb_buffer_append_string(&comment, token->value);
135
187
  token_free(token, parser->allocator);
@@ -137,19 +189,31 @@ static AST_HTML_COMMENT_NODE_T* parser_parse_html_comment(parser_T* parser) {
137
189
 
138
190
  parser_append_literal_node_from_buffer(parser, &comment, children, start);
139
191
 
140
- token_T* comment_end = NULL;
141
-
142
- if (token_is(parser, TOKEN_HTML_COMMENT_INVALID_END)) {
192
+ if (comment_end != NULL) {
193
+ // closed by <!-->
194
+ } else if (token_is(parser, TOKEN_HTML_COMMENT_INVALID_END)) {
143
195
  comment_end = parser_advance(parser);
144
196
  append_invalid_comment_closing_tag_error(
145
197
  comment_end,
146
198
  comment_end->location.start,
147
199
  comment_end->location.end,
148
200
  parser->allocator,
149
- errors
201
+ &errors,
202
+ &parser->options
203
+ );
204
+ } else if (token_is(parser, TOKEN_EOF)) {
205
+ append_unclosed_comment_error(
206
+ comment_start,
207
+ comment_start->location.start,
208
+ parser->current_token->location.start,
209
+ parser->allocator,
210
+ &errors,
211
+ &parser->options
150
212
  );
213
+
214
+ comment_end = parser_advance(parser);
151
215
  } else {
152
- comment_end = parser_consume_expected(parser, TOKEN_HTML_COMMENT_END, errors);
216
+ comment_end = parser_consume_expected(parser, TOKEN_HTML_COMMENT_END, &errors);
153
217
  }
154
218
 
155
219
  AST_HTML_COMMENT_NODE_T* comment_node = ast_html_comment_node_init(
@@ -163,19 +227,17 @@ static AST_HTML_COMMENT_NODE_T* parser_parse_html_comment(parser_T* parser) {
163
227
  );
164
228
 
165
229
  hb_buffer_free(&comment);
166
- token_free(comment_start, parser->allocator);
167
- token_free(comment_end, parser->allocator);
168
230
 
169
231
  return comment_node;
170
232
  }
171
233
 
172
234
  static AST_HTML_DOCTYPE_NODE_T* parser_parse_html_doctype(parser_T* parser) {
173
- hb_array_T* errors = hb_array_init(8, parser->allocator);
235
+ hb_array_T* errors = NULL;
174
236
  hb_array_T* children = hb_array_init(8, parser->allocator);
175
237
  hb_buffer_T content;
176
238
  hb_buffer_init(&content, 64, parser->allocator);
177
239
 
178
- token_T* tag_opening = parser_consume_expected(parser, TOKEN_HTML_DOCTYPE, errors);
240
+ token_T* tag_opening = parser_consume_expected(parser, TOKEN_HTML_DOCTYPE, &errors);
179
241
 
180
242
  position_T start = parser->current_token->location.start;
181
243
 
@@ -183,20 +245,20 @@ static AST_HTML_DOCTYPE_NODE_T* parser_parse_html_doctype(parser_T* parser) {
183
245
  if (token_is(parser, TOKEN_ERB_START)) {
184
246
  parser_append_literal_node_from_buffer(parser, &content, children, start);
185
247
 
186
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
248
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
187
249
  hb_array_append(children, erb_node);
188
250
 
189
251
  continue;
190
252
  }
191
253
 
192
- token_T* token = parser_consume_expected(parser, parser->current_token->type, errors);
254
+ token_T* token = parser_consume_expected(parser, parser->current_token->type, &errors);
193
255
  hb_buffer_append_string(&content, token->value);
194
256
  token_free(token, parser->allocator);
195
257
  }
196
258
 
197
259
  parser_append_literal_node_from_buffer(parser, &content, children, start);
198
260
 
199
- token_T* tag_closing = parser_consume_expected(parser, TOKEN_HTML_TAG_END, errors);
261
+ token_T* tag_closing = parser_consume_expected(parser, TOKEN_HTML_TAG_END, &errors);
200
262
 
201
263
  AST_HTML_DOCTYPE_NODE_T* doctype = ast_html_doctype_node_init(
202
264
  tag_opening,
@@ -208,20 +270,20 @@ static AST_HTML_DOCTYPE_NODE_T* parser_parse_html_doctype(parser_T* parser) {
208
270
  parser->allocator
209
271
  );
210
272
 
211
- token_free(tag_opening, parser->allocator);
212
- token_free(tag_closing, parser->allocator);
213
273
  hb_buffer_free(&content);
214
274
 
215
275
  return doctype;
216
276
  }
217
277
 
218
278
  static AST_XML_DECLARATION_NODE_T* parser_parse_xml_declaration(parser_T* parser) {
219
- hb_array_T* errors = hb_array_init(8, parser->allocator);
279
+ hb_array_T* errors = NULL;
220
280
  hb_array_T* children = hb_array_init(8, parser->allocator);
221
281
  hb_buffer_T content;
222
282
  hb_buffer_init(&content, 64, parser->allocator);
223
283
 
224
- token_T* tag_opening = parser_consume_expected(parser, TOKEN_XML_DECLARATION, errors);
284
+ token_T* tag_opening = parser_consume_expected(parser, TOKEN_XML_DECLARATION, &errors);
285
+
286
+ parser->xml_document = true;
225
287
 
226
288
  position_T start = parser->current_token->location.start;
227
289
 
@@ -229,7 +291,7 @@ static AST_XML_DECLARATION_NODE_T* parser_parse_xml_declaration(parser_T* parser
229
291
  if (token_is(parser, TOKEN_ERB_START)) {
230
292
  parser_append_literal_node_from_buffer(parser, &content, children, start);
231
293
 
232
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
294
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
233
295
  hb_array_append(children, erb_node);
234
296
 
235
297
  start = parser->current_token->location.start;
@@ -244,7 +306,7 @@ static AST_XML_DECLARATION_NODE_T* parser_parse_xml_declaration(parser_T* parser
244
306
 
245
307
  parser_append_literal_node_from_buffer(parser, &content, children, start);
246
308
 
247
- token_T* tag_closing = parser_consume_expected(parser, TOKEN_XML_DECLARATION_END, errors);
309
+ token_T* tag_closing = parser_consume_expected(parser, TOKEN_XML_DECLARATION_END, &errors);
248
310
 
249
311
  AST_XML_DECLARATION_NODE_T* xml_declaration = ast_xml_declaration_node_init(
250
312
  tag_opening,
@@ -256,14 +318,61 @@ static AST_XML_DECLARATION_NODE_T* parser_parse_xml_declaration(parser_T* parser
256
318
  parser->allocator
257
319
  );
258
320
 
259
- token_free(tag_opening, parser->allocator);
260
- token_free(tag_closing, parser->allocator);
261
321
  hb_buffer_free(&content);
262
322
 
263
323
  return xml_declaration;
264
324
  }
265
325
 
266
- static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_array_T* document_errors) {
326
+ static AST_XML_PROCESSING_INSTRUCTION_NODE_T* parser_parse_xml_processing_instruction(parser_T* parser) {
327
+ hb_array_T* errors = NULL;
328
+ hb_array_T* children = hb_array_init(8, parser->allocator);
329
+ hb_buffer_T content;
330
+ hb_buffer_init(&content, 64, parser->allocator);
331
+
332
+ token_T* tag_opening = parser_consume_expected(parser, TOKEN_XML_PROCESSING_INSTRUCTION_START, &errors);
333
+ token_T* target = parser_consume_expected(parser, TOKEN_IDENTIFIER, &errors);
334
+
335
+ position_T start = parser->current_token->location.start;
336
+
337
+ while (token_is_none_of(parser, TOKEN_XML_DECLARATION_END, TOKEN_HTML_TAG_END, TOKEN_EOF)) {
338
+ if (token_is(parser, TOKEN_ERB_START)) {
339
+ parser_append_literal_node_from_buffer(parser, &content, children, start);
340
+
341
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
342
+ hb_array_append(children, erb_node);
343
+
344
+ start = parser->current_token->location.start;
345
+
346
+ continue;
347
+ }
348
+
349
+ token_T* token = parser_advance(parser);
350
+ hb_buffer_append_string(&content, token->value);
351
+ token_free(token, parser->allocator);
352
+ }
353
+
354
+ parser_append_literal_node_from_buffer(parser, &content, children, start);
355
+
356
+ token_type_T closing_type = token_is(parser, TOKEN_HTML_TAG_END) ? TOKEN_HTML_TAG_END : TOKEN_XML_DECLARATION_END;
357
+ token_T* tag_closing = parser_consume_expected(parser, closing_type, &errors);
358
+
359
+ AST_XML_PROCESSING_INSTRUCTION_NODE_T* processing_instruction = ast_xml_processing_instruction_node_init(
360
+ tag_opening,
361
+ target,
362
+ children,
363
+ tag_closing,
364
+ tag_opening->location.start,
365
+ tag_closing->location.end,
366
+ errors,
367
+ parser->allocator
368
+ );
369
+
370
+ hb_buffer_free(&content);
371
+
372
+ return processing_instruction;
373
+ }
374
+
375
+ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_array_T** document_errors) {
267
376
  position_T start = parser->current_token->location.start;
268
377
 
269
378
  hb_buffer_T content;
@@ -275,6 +384,8 @@ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_arra
275
384
  TOKEN_HTML_TAG_START_CLOSE,
276
385
  TOKEN_HTML_DOCTYPE,
277
386
  TOKEN_HTML_COMMENT_START,
387
+ TOKEN_XML_DECLARATION,
388
+ TOKEN_XML_PROCESSING_INSTRUCTION_START,
278
389
  TOKEN_ERB_START,
279
390
  TOKEN_EOF
280
391
  )) {
@@ -295,7 +406,13 @@ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_arra
295
406
  position_T stray_end = peek_token->location.end;
296
407
  token_free(peek_token, parser->allocator);
297
408
 
298
- append_stray_erb_closing_tag_error(stray_start, stray_end, parser->allocator, document_errors);
409
+ append_stray_erb_closing_tag_error(
410
+ stray_start,
411
+ stray_end,
412
+ parser->allocator,
413
+ document_errors,
414
+ &parser->options
415
+ );
299
416
 
300
417
  token_T* percent = parser_advance(parser);
301
418
  hb_buffer_append_string(&content, percent->value);
@@ -316,7 +433,7 @@ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_arra
316
433
  token_free(token, parser->allocator);
317
434
  }
318
435
 
319
- hb_array_T* errors = hb_array_init(8, parser->allocator);
436
+ hb_array_T* errors = NULL;
320
437
 
321
438
  AST_HTML_TEXT_NODE_T* text_node = NULL;
322
439
 
@@ -334,22 +451,42 @@ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_arra
334
451
  return text_node;
335
452
  }
336
453
 
454
+ static bool parser_token_starts_tag(const parser_T* parser) {
455
+ return token_is_any_of(
456
+ parser,
457
+ TOKEN_HTML_TAG_START,
458
+ TOKEN_HTML_TAG_START_CLOSE,
459
+ TOKEN_HTML_COMMENT_START,
460
+ TOKEN_HTML_DOCTYPE,
461
+ TOKEN_XML_PROCESSING_INSTRUCTION_START
462
+ );
463
+ }
464
+
337
465
  static AST_HTML_ATTRIBUTE_NAME_NODE_T* parser_parse_html_attribute_name(parser_T* parser) {
338
- hb_array_T* errors = hb_array_init(8, parser->allocator);
466
+ hb_array_T* errors = NULL;
339
467
  hb_array_T* children = hb_array_init(8, parser->allocator);
340
468
  hb_buffer_T buffer;
341
469
  hb_buffer_init(&buffer, 128, parser->allocator);
342
470
  position_T start = parser->current_token->location.start;
343
471
 
472
+ // <div =foo>
473
+ if (token_is(parser, TOKEN_EQUALS)) {
474
+ token_T* equals = parser_advance(parser);
475
+ hb_buffer_append_string(&buffer, equals->value);
476
+ token_free(equals, parser->allocator);
477
+ }
478
+
344
479
  while (token_is_none_of(
345
- parser,
346
- TOKEN_EQUALS,
347
- TOKEN_WHITESPACE,
348
- TOKEN_NEWLINE,
349
- TOKEN_HTML_TAG_END,
350
- TOKEN_HTML_TAG_SELF_CLOSE,
351
- TOKEN_EOF
352
- )) {
480
+ parser,
481
+ TOKEN_EQUALS,
482
+ TOKEN_WHITESPACE,
483
+ TOKEN_NEWLINE,
484
+ TOKEN_SLASH,
485
+ TOKEN_HTML_TAG_END,
486
+ TOKEN_HTML_TAG_SELF_CLOSE,
487
+ TOKEN_EOF
488
+ )
489
+ && !parser_token_starts_tag(parser)) {
353
490
  if (token_is(parser, TOKEN_ERB_START)) {
354
491
  hb_string_T tag = parser->current_token->value;
355
492
  bool is_output_tag = (tag.length >= 3 && tag.data[2] == '=');
@@ -363,13 +500,24 @@ static AST_HTML_ATTRIBUTE_NAME_NODE_T* parser_parse_html_attribute_name(parser_T
363
500
 
364
501
  parser_append_literal_node_from_buffer(parser, &buffer, children, start);
365
502
 
366
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
503
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
367
504
  hb_array_append(children, erb_node);
368
505
 
369
506
  start = parser->current_token->location.start;
370
507
  continue;
371
508
  }
372
509
 
510
+ if (token_is(parser, TOKEN_QUOTE)) {
511
+ append_unexpected_character_in_attribute_name_error(
512
+ parser->current_token,
513
+ parser->current_token->location.start,
514
+ parser->current_token->location.end,
515
+ parser->allocator,
516
+ &errors,
517
+ &parser->options
518
+ );
519
+ }
520
+
373
521
  token_T* token = parser_advance(parser);
374
522
  hb_buffer_append_string(&buffer, token->value);
375
523
  token_free(token, parser->allocator);
@@ -402,7 +550,7 @@ static AST_HTML_ATTRIBUTE_NAME_NODE_T* parser_parse_html_attribute_name(parser_T
402
550
  static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value(
403
551
  parser_T* parser,
404
552
  hb_array_T* children,
405
- hb_array_T* errors
553
+ hb_array_T** errors
406
554
  ) {
407
555
  hb_buffer_T buffer;
408
556
  hb_buffer_init(&buffer, 512, parser->allocator);
@@ -450,26 +598,23 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
450
598
  opening_quote->location.start,
451
599
  parser->current_token->location.start,
452
600
  parser->allocator,
453
- errors
601
+ errors,
602
+ &parser->options
454
603
  );
455
604
 
456
605
  parser_append_literal_node_from_buffer(parser, &buffer, children, start);
457
606
  hb_buffer_free(&buffer);
458
607
 
459
- AST_HTML_ATTRIBUTE_VALUE_NODE_T* attribute_value = ast_html_attribute_value_node_init(
608
+ return ast_html_attribute_value_node_init(
460
609
  opening_quote,
461
610
  children,
462
611
  NULL,
463
612
  true,
464
613
  opening_quote->location.start,
465
614
  parser->current_token->location.start,
466
- errors,
615
+ *errors,
467
616
  parser->allocator
468
617
  );
469
-
470
- token_free(opening_quote, parser->allocator);
471
-
472
- return attribute_value;
473
618
  }
474
619
 
475
620
  bool buffer_ends_with_whitespace = buffer.length > 0 && is_whitespace(buffer.value[buffer.length - 1]);
@@ -497,26 +642,23 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
497
642
  opening_quote->location.start,
498
643
  parser->current_token->location.start,
499
644
  parser->allocator,
500
- errors
645
+ errors,
646
+ &parser->options
501
647
  );
502
648
 
503
649
  parser_append_literal_node_from_buffer(parser, &buffer, children, start);
504
650
  hb_buffer_free(&buffer);
505
651
 
506
- AST_HTML_ATTRIBUTE_VALUE_NODE_T* attribute_value = ast_html_attribute_value_node_init(
652
+ return ast_html_attribute_value_node_init(
507
653
  opening_quote,
508
654
  children,
509
655
  NULL,
510
656
  true,
511
657
  opening_quote->location.start,
512
658
  parser->current_token->location.start,
513
- errors,
659
+ *errors,
514
660
  parser->allocator
515
661
  );
516
-
517
- token_free(opening_quote, parser->allocator);
518
-
519
- return attribute_value;
520
662
  }
521
663
  }
522
664
 
@@ -536,176 +678,133 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
536
678
  parser->current_token = lexer_next_token(parser->lexer);
537
679
  }
538
680
 
539
- if (token_is(parser, TOKEN_QUOTE) && opening_quote != NULL
540
- && hb_string_equals(parser->current_token->value, opening_quote->value)) {
541
- lexer_state_snapshot_T saved_state = lexer_save_state(parser->lexer);
542
-
543
- token_T* potential_closing = parser->current_token;
544
- parser->current_token = lexer_next_token(parser->lexer);
545
-
546
- if (token_is(parser, TOKEN_IDENTIFIER) || token_is(parser, TOKEN_CHARACTER)) {
547
- append_unexpected_error(
548
- hb_string("Unescaped quote character in attribute value"),
549
- hb_string("HTML entity (&apos;/&quot;) or different quote style"),
550
- opening_quote->value,
551
- potential_closing->location.start,
552
- potential_closing->location.end,
553
- parser->allocator,
554
- errors
555
- );
556
-
557
- lexer_restore_state(parser->lexer, saved_state);
558
-
559
- token_free(parser->current_token, parser->allocator);
560
- parser->current_token = potential_closing;
561
-
562
- hb_buffer_append_string(&buffer, parser->current_token->value);
563
- token_free(parser->current_token, parser->allocator);
564
- parser->current_token = lexer_next_token(parser->lexer);
565
-
566
- while (!token_is(parser, TOKEN_EOF)
567
- && !(
568
- token_is(parser, TOKEN_QUOTE) && opening_quote != NULL
569
- && hb_string_equals(parser->current_token->value, opening_quote->value)
570
- )) {
571
- if (token_is(parser, TOKEN_ERB_START)) {
572
- parser_append_literal_node_from_buffer(parser, &buffer, children, start);
573
-
574
- hb_array_append(children, parser_parse_erb_tag(parser));
575
-
576
- start = parser->current_token->location.start;
577
-
578
- continue;
579
- }
580
-
581
- hb_buffer_append_string(&buffer, parser->current_token->value);
582
- token_free(parser->current_token, parser->allocator);
583
-
584
- parser->current_token = lexer_next_token(parser->lexer);
585
- }
586
- } else {
587
- token_free(parser->current_token, parser->allocator);
588
- parser->current_token = potential_closing;
589
-
590
- lexer_restore_state(parser->lexer, saved_state);
591
- }
592
- }
593
-
594
681
  parser_append_literal_node_from_buffer(parser, &buffer, children, start);
595
682
  hb_buffer_free(&buffer);
596
683
 
597
684
  token_T* closing_quote = parser_consume_expected(parser, TOKEN_QUOTE, errors);
598
685
 
599
- AST_HTML_ATTRIBUTE_VALUE_NODE_T* attribute_value = ast_html_attribute_value_node_init(
686
+ return ast_html_attribute_value_node_init(
600
687
  opening_quote,
601
688
  children,
602
689
  closing_quote,
603
690
  true,
604
691
  opening_quote->location.start,
605
692
  closing_quote->location.end,
606
- errors,
693
+ *errors,
607
694
  parser->allocator
608
695
  );
696
+ }
609
697
 
610
- token_free(opening_quote, parser->allocator);
611
- token_free(closing_quote, parser->allocator);
612
-
613
- return attribute_value;
698
+ static bool parser_attribute_value_is_missing(const parser_T* parser) {
699
+ return token_is_any_of(parser, TOKEN_HTML_TAG_END, TOKEN_HTML_TAG_SELF_CLOSE, TOKEN_EOF)
700
+ || parser_token_starts_tag(parser);
614
701
  }
615
702
 
616
703
  static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_html_attribute_value(parser_T* parser) {
617
704
  hb_array_T* children = hb_array_init(8, parser->allocator);
618
- hb_array_T* errors = hb_array_init(8, parser->allocator);
705
+ hb_array_T* errors = NULL;
619
706
 
620
- // <div id=<%= "home" %>>
621
- if (token_is(parser, TOKEN_ERB_START)) {
622
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
623
- hb_array_append(children, erb_node);
707
+ // <div id="home">
708
+ if (token_is(parser, TOKEN_QUOTE)) { return parser_parse_quoted_html_attribute_value(parser, children, &errors); }
624
709
 
625
- return ast_html_attribute_value_node_init(
626
- NULL,
627
- children,
628
- NULL,
629
- false,
630
- erb_node->base.location.start,
631
- erb_node->base.location.end,
632
- errors,
633
- parser->allocator
634
- );
635
- }
710
+ // <div id=home> or <div id=<%= "home" %>>
711
+ hb_buffer_T buffer;
712
+ hb_buffer_init(&buffer, 128, parser->allocator);
713
+ position_T value_start = parser->current_token->location.start;
714
+ position_T value_end = value_start;
715
+ position_T start = value_start;
716
+
717
+ while (
718
+ token_is_none_of(parser, TOKEN_WHITESPACE, TOKEN_NEWLINE, TOKEN_HTML_TAG_END, TOKEN_HTML_TAG_SELF_CLOSE, TOKEN_EOF)
719
+ && !parser_token_starts_tag(parser)
720
+ ) {
721
+ if (token_is(parser, TOKEN_ERB_START)) {
722
+ parser_append_literal_node_from_buffer(parser, &buffer, children, start);
636
723
 
637
- // <div id=home>
638
- if (token_is(parser, TOKEN_IDENTIFIER)) {
639
- token_T* identifier = parser_consume_expected(parser, TOKEN_IDENTIFIER, errors);
640
- AST_LITERAL_NODE_T* literal = ast_literal_node_init_from_token(identifier, parser->allocator);
641
- token_free(identifier, parser->allocator);
724
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
725
+ hb_array_append(children, erb_node);
642
726
 
643
- hb_array_append(children, literal);
727
+ value_end = erb_node->location.end;
728
+ start = parser->current_token->location.start;
644
729
 
645
- return ast_html_attribute_value_node_init(
646
- NULL,
647
- children,
648
- NULL,
649
- false,
650
- literal->base.location.start,
651
- literal->base.location.end,
652
- errors,
653
- parser->allocator
654
- );
655
- }
730
+ continue;
731
+ }
656
732
 
657
- // <div id="home">
658
- if (token_is(parser, TOKEN_QUOTE)) { return parser_parse_quoted_html_attribute_value(parser, children, errors); }
733
+ if (token_is_any_of(parser, TOKEN_QUOTE, TOKEN_BACKTICK, TOKEN_EQUALS)) {
734
+ append_unexpected_character_in_unquoted_attribute_value_error(
735
+ parser->current_token,
736
+ parser->current_token->location.start,
737
+ parser->current_token->location.end,
738
+ parser->allocator,
739
+ &errors,
740
+ &parser->options
741
+ );
742
+ }
659
743
 
660
- if (token_is(parser, TOKEN_BACKTICK)) {
661
744
  token_T* token = parser_advance(parser);
662
- position_T start = token->location.start;
663
- position_T end = token->location.end;
664
-
665
- append_unexpected_error(
666
- hb_string("Invalid quote character for HTML attribute"),
667
- hb_string("single quote (') or double quote (\")"),
668
- hb_string("a backtick"),
669
- start,
670
- end,
671
- parser->allocator,
672
- errors
673
- );
745
+ hb_buffer_append_string(&buffer, token->value);
746
+ value_end = token->location.end;
747
+ token_free(token, parser->allocator);
748
+ }
674
749
 
675
- AST_HTML_ATTRIBUTE_VALUE_NODE_T* value =
676
- ast_html_attribute_value_node_init(NULL, children, NULL, false, start, end, errors, parser->allocator);
750
+ parser_append_literal_node_from_buffer(parser, &buffer, children, start);
751
+ hb_buffer_free(&buffer);
677
752
 
678
- token_free(token, parser->allocator);
753
+ return ast_html_attribute_value_node_init(
754
+ NULL,
755
+ children,
756
+ NULL,
757
+ false,
758
+ value_start,
759
+ value_end,
760
+ errors,
761
+ parser->allocator
762
+ );
763
+ }
679
764
 
680
- return value;
681
- }
765
+ static AST_HTML_ATTRIBUTE_NODE_T* parser_parse_html_attribute_missing_value(
766
+ parser_T* parser,
767
+ AST_HTML_ATTRIBUTE_NAME_NODE_T* attribute_name,
768
+ token_T* equals
769
+ ) {
770
+ hb_array_T* errors = NULL;
771
+ hb_string_T attribute_name_string = hb_string("unknown");
682
772
 
683
- char* expected = token_types_to_friendly_string(parser->allocator, TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START);
773
+ if (hb_array_size(attribute_name->children) > 0) {
774
+ AST_LITERAL_NODE_T* first_child = (AST_LITERAL_NODE_T*) hb_array_get(attribute_name->children, 0);
684
775
 
685
- append_unexpected_error(
686
- hb_string("Unexpected Token"),
687
- hb_string(expected),
688
- token_type_to_friendly_string(parser->current_token->type),
776
+ if (first_child && !hb_string_is_empty(first_child->content)) { attribute_name_string = first_child->content; }
777
+ }
778
+
779
+ append_missing_attribute_value_error(
780
+ attribute_name_string,
781
+ equals->location.start,
689
782
  parser->current_token->location.start,
690
- parser->current_token->location.end,
691
783
  parser->allocator,
692
- errors
784
+ &errors,
785
+ &parser->options
693
786
  );
694
787
 
695
- hb_allocator_dealloc(parser->allocator, expected);
696
-
697
- AST_HTML_ATTRIBUTE_VALUE_NODE_T* value = ast_html_attribute_value_node_init(
788
+ AST_HTML_ATTRIBUTE_VALUE_NODE_T* empty_value = ast_html_attribute_value_node_init(
698
789
  NULL,
699
- children,
790
+ hb_array_init(8, parser->allocator),
700
791
  NULL,
701
792
  false,
793
+ equals->location.end,
702
794
  parser->current_token->location.start,
703
- parser->current_token->location.end,
704
795
  errors,
705
796
  parser->allocator
706
797
  );
707
798
 
708
- return value;
799
+ return ast_html_attribute_node_init(
800
+ attribute_name,
801
+ equals,
802
+ empty_value,
803
+ attribute_name->base.location.start,
804
+ parser->current_token->location.start,
805
+ NULL,
806
+ parser->allocator
807
+ );
709
808
  }
710
809
 
711
810
  static AST_HTML_ATTRIBUTE_NODE_T* parser_parse_html_attribute(parser_T* parser) {
@@ -778,12 +877,18 @@ static AST_HTML_ATTRIBUTE_NODE_T* parser_parse_html_attribute(parser_T* parser)
778
877
 
779
878
  char* arena_copy = hb_allocator_strndup(parser->allocator, equals_buffer.value, equals_buffer.length);
780
879
  equals_with_whitespace->value = hb_string_from_data(arena_copy, equals_buffer.length);
880
+ equals_with_whitespace->owns_value = true;
781
881
 
782
882
  hb_buffer_free(&equals_buffer);
783
883
 
784
884
  equals_with_whitespace->location = (location_T) { .start = equals_start, .end = equals_end };
785
885
  equals_with_whitespace->range = (range_T) { .from = range_start, .to = range_end };
786
886
 
887
+ // <div class = >
888
+ if (parser_attribute_value_is_missing(parser)) {
889
+ return parser_parse_html_attribute_missing_value(parser, attribute_name, equals_with_whitespace);
890
+ }
891
+
787
892
  AST_HTML_ATTRIBUTE_VALUE_NODE_T* attribute_value = parser_parse_html_attribute_value(parser);
788
893
 
789
894
  return ast_html_attribute_node_init(
@@ -816,53 +921,13 @@ static AST_HTML_ATTRIBUTE_NODE_T* parser_parse_html_attribute(parser_T* parser)
816
921
  parser_consume_whitespace(parser, NULL);
817
922
 
818
923
  // <div class= >
819
- if (token_is(parser, TOKEN_HTML_TAG_END) || token_is(parser, TOKEN_HTML_TAG_SELF_CLOSE)) {
820
- hb_array_T* errors = hb_array_init(8, parser->allocator);
821
- hb_string_T attribute_name_string = hb_string("unknown");
822
-
823
- if (hb_array_size(attribute_name->children) > 0) {
824
- AST_LITERAL_NODE_T* first_child = (AST_LITERAL_NODE_T*) hb_array_get(attribute_name->children, 0);
825
-
826
- if (first_child && !hb_string_is_empty(first_child->content)) { attribute_name_string = first_child->content; }
827
- }
828
-
829
- append_missing_attribute_value_error(
830
- attribute_name_string,
831
- equals->location.start,
832
- parser->current_token->location.start,
833
- parser->allocator,
834
- errors
835
- );
836
-
837
- AST_HTML_ATTRIBUTE_VALUE_NODE_T* empty_value = ast_html_attribute_value_node_init(
838
- NULL,
839
- hb_array_init(8, parser->allocator),
840
- NULL,
841
- false,
842
- equals->location.end,
843
- parser->current_token->location.start,
844
- errors,
845
- parser->allocator
846
- );
847
-
848
- AST_HTML_ATTRIBUTE_NODE_T* attribute_node = ast_html_attribute_node_init(
849
- attribute_name,
850
- equals,
851
- empty_value,
852
- attribute_name->base.location.start,
853
- parser->current_token->location.start,
854
- NULL,
855
- parser->allocator
856
- );
857
-
858
- token_free(equals, parser->allocator);
859
-
860
- return attribute_node;
924
+ if (parser_attribute_value_is_missing(parser)) {
925
+ return parser_parse_html_attribute_missing_value(parser, attribute_name, equals);
861
926
  }
862
927
 
863
928
  AST_HTML_ATTRIBUTE_VALUE_NODE_T* attribute_value = parser_parse_html_attribute_value(parser);
864
929
 
865
- AST_HTML_ATTRIBUTE_NODE_T* attribute_node = ast_html_attribute_node_init(
930
+ return ast_html_attribute_node_init(
866
931
  attribute_name,
867
932
  equals,
868
933
  attribute_value,
@@ -871,10 +936,6 @@ static AST_HTML_ATTRIBUTE_NODE_T* parser_parse_html_attribute(parser_T* parser)
871
936
  NULL,
872
937
  parser->allocator
873
938
  );
874
-
875
- token_free(equals, parser->allocator);
876
-
877
- return attribute_node;
878
939
  }
879
940
 
880
941
  return ast_html_attribute_node_init(
@@ -1030,25 +1091,28 @@ static void parser_handle_whitespace_in_open_tag(parser_T* parser, hb_array_T* c
1030
1091
  }
1031
1092
 
1032
1093
  static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1033
- hb_array_T* errors = hb_array_init(8, parser->allocator);
1094
+ hb_array_T* errors = NULL;
1034
1095
  hb_array_T* children = hb_array_init(8, parser->allocator);
1035
1096
 
1036
- token_T* tag_start = parser_consume_expected(parser, TOKEN_HTML_TAG_START, errors);
1037
- token_T* tag_name = parser_consume_expected(parser, TOKEN_IDENTIFIER, errors);
1097
+ token_T* tag_start = parser_consume_expected(parser, TOKEN_HTML_TAG_START, &errors);
1098
+ token_T* tag_name = parser_consume_expected(parser, TOKEN_IDENTIFIER, &errors);
1099
+
1100
+ parser_consume_dot_notation_segments(parser, tag_name, &errors);
1038
1101
 
1039
- parser_consume_dot_notation_segments(parser, tag_name, errors);
1102
+ bool previous_was_quoted_value = false;
1040
1103
 
1041
1104
  while (token_is_none_of(parser, TOKEN_HTML_TAG_END, TOKEN_HTML_TAG_SELF_CLOSE, TOKEN_EOF)) {
1042
- if (token_is_any_of(parser, TOKEN_HTML_TAG_START, TOKEN_HTML_TAG_START_CLOSE)) {
1105
+ if (parser_token_starts_tag(parser)) {
1043
1106
  append_unclosed_open_tag_error(
1044
1107
  tag_name,
1045
1108
  tag_name->location.start,
1046
1109
  parser->current_token->location.start,
1047
1110
  parser->allocator,
1048
- errors
1111
+ &errors,
1112
+ &parser->options
1049
1113
  );
1050
1114
 
1051
- AST_HTML_OPEN_TAG_NODE_T* open_tag_node = ast_html_open_tag_node_init(
1115
+ return ast_html_open_tag_node_init(
1052
1116
  tag_start,
1053
1117
  tag_name,
1054
1118
  NULL,
@@ -1059,48 +1123,21 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1059
1123
  errors,
1060
1124
  parser->allocator
1061
1125
  );
1062
-
1063
- token_free(tag_start, parser->allocator);
1064
- token_free(tag_name, parser->allocator);
1065
-
1066
- return open_tag_node;
1067
1126
  }
1068
1127
 
1069
1128
  if (token_is_any_of(parser, TOKEN_WHITESPACE, TOKEN_NEWLINE)) {
1129
+ previous_was_quoted_value = false;
1070
1130
  parser_handle_whitespace_in_open_tag(parser, children);
1071
1131
  continue;
1072
1132
  }
1073
1133
 
1074
- if (parser->current_token->type == TOKEN_IDENTIFIER) {
1075
- hb_array_append(children, parser_parse_html_attribute(parser));
1076
- continue;
1077
- }
1078
-
1079
- if (parser->current_token->type == TOKEN_ERB_START) {
1134
+ if (token_is(parser, TOKEN_ERB_START)) {
1135
+ previous_was_quoted_value = false;
1080
1136
  parser_handle_erb_in_open_tag(parser, children);
1081
1137
  continue;
1082
1138
  }
1083
1139
 
1084
- if (parser->current_token->type == TOKEN_AT) {
1085
- hb_array_append(children, parser_parse_html_attribute(parser));
1086
- continue;
1087
- }
1088
-
1089
- if (parser->current_token->type == TOKEN_COLON) {
1090
- lexer_T lexer_copy = *parser->lexer;
1091
- token_T* next_token = lexer_next_token(&lexer_copy);
1092
-
1093
- if (next_token && next_token->type == TOKEN_IDENTIFIER) {
1094
- token_free(next_token, parser->allocator);
1095
- hb_array_append(children, parser_parse_html_attribute(parser));
1096
-
1097
- continue;
1098
- }
1099
-
1100
- token_free(next_token, parser->allocator);
1101
- }
1102
-
1103
- if (parser->current_token->type == TOKEN_PERCENT) {
1140
+ if (token_is(parser, TOKEN_PERCENT)) {
1104
1141
  lexer_T lexer_copy = *parser->lexer;
1105
1142
  token_T* peek_token = lexer_next_token(&lexer_copy);
1106
1143
 
@@ -1109,7 +1146,7 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1109
1146
  position_T stray_end = peek_token->location.end;
1110
1147
  token_free(peek_token, parser->allocator);
1111
1148
 
1112
- append_stray_erb_closing_tag_error(stray_start, stray_end, parser->allocator, errors);
1149
+ append_stray_erb_closing_tag_error(stray_start, stray_end, parser->allocator, &errors, &parser->options);
1113
1150
 
1114
1151
  token_T* percent = parser_advance(parser);
1115
1152
  token_T* gt = parser_advance(parser);
@@ -1121,22 +1158,73 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1121
1158
  token_free(percent, parser->allocator);
1122
1159
  token_free(gt, parser->allocator);
1123
1160
 
1161
+ previous_was_quoted_value = false;
1124
1162
  continue;
1125
1163
  }
1126
1164
 
1127
1165
  token_free(peek_token, parser->allocator);
1128
1166
  }
1129
1167
 
1130
- parser_append_unexpected_error(
1131
- parser,
1132
- errors,
1133
- "Unexpected Token",
1134
- TOKEN_IDENTIFIER,
1135
- TOKEN_AT,
1136
- TOKEN_ERB_START,
1137
- TOKEN_WHITESPACE,
1138
- TOKEN_NEWLINE
1139
- );
1168
+ // <div / class="a">
1169
+ if (token_is(parser, TOKEN_SLASH)) {
1170
+ token_T* solidus = parser_advance(parser);
1171
+
1172
+ append_unexpected_solidus_in_tag_error(
1173
+ solidus,
1174
+ solidus->location.start,
1175
+ solidus->location.end,
1176
+ parser->allocator,
1177
+ &errors,
1178
+ &parser->options
1179
+ );
1180
+
1181
+ token_free(solidus, parser->allocator);
1182
+ previous_was_quoted_value = false;
1183
+ continue;
1184
+ }
1185
+
1186
+ if (token_is(parser, TOKEN_ERROR)) {
1187
+ parser_append_unexpected_error(
1188
+ parser,
1189
+ &errors,
1190
+ "Unexpected Token",
1191
+ TOKEN_IDENTIFIER,
1192
+ TOKEN_AT,
1193
+ TOKEN_ERB_START,
1194
+ TOKEN_WHITESPACE,
1195
+ TOKEN_NEWLINE
1196
+ );
1197
+ continue;
1198
+ }
1199
+
1200
+ // <div class="a"id="b">
1201
+ if (previous_was_quoted_value) {
1202
+ append_missing_whitespace_between_attributes_error(
1203
+ parser->current_token,
1204
+ parser->current_token->location.start,
1205
+ parser->current_token->location.end,
1206
+ parser->allocator,
1207
+ &errors,
1208
+ &parser->options
1209
+ );
1210
+ }
1211
+
1212
+ // <div =foo>
1213
+ if (token_is(parser, TOKEN_EQUALS)) {
1214
+ append_unexpected_equals_sign_before_attribute_name_error(
1215
+ parser->current_token,
1216
+ parser->current_token->location.start,
1217
+ parser->current_token->location.end,
1218
+ parser->allocator,
1219
+ &errors,
1220
+ &parser->options
1221
+ );
1222
+ }
1223
+
1224
+ AST_HTML_ATTRIBUTE_NODE_T* attribute = parser_parse_html_attribute(parser);
1225
+ hb_array_append(children, attribute);
1226
+
1227
+ previous_was_quoted_value = attribute->value != NULL && attribute->value->close_quote != NULL;
1140
1228
  }
1141
1229
 
1142
1230
  if (token_is(parser, TOKEN_EOF)) {
@@ -1145,10 +1233,11 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1145
1233
  tag_name->location.start,
1146
1234
  parser->current_token->location.start,
1147
1235
  parser->allocator,
1148
- errors
1236
+ &errors,
1237
+ &parser->options
1149
1238
  );
1150
1239
 
1151
- AST_HTML_OPEN_TAG_NODE_T* open_tag_node = ast_html_open_tag_node_init(
1240
+ return ast_html_open_tag_node_init(
1152
1241
  tag_start,
1153
1242
  tag_name,
1154
1243
  NULL,
@@ -1159,11 +1248,6 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1159
1248
  errors,
1160
1249
  parser->allocator
1161
1250
  );
1162
-
1163
- token_free(tag_start, parser->allocator);
1164
- token_free(tag_name, parser->allocator);
1165
-
1166
- return open_tag_node;
1167
1251
  }
1168
1252
 
1169
1253
  bool is_self_closing = false;
@@ -1171,7 +1255,7 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1171
1255
  token_T* tag_end = parser_consume_if_present(parser, TOKEN_HTML_TAG_END);
1172
1256
 
1173
1257
  if (tag_end == NULL) {
1174
- tag_end = parser_consume_expected(parser, TOKEN_HTML_TAG_SELF_CLOSE, errors);
1258
+ tag_end = parser_consume_expected(parser, TOKEN_HTML_TAG_SELF_CLOSE, &errors);
1175
1259
 
1176
1260
  if (tag_end == NULL) {
1177
1261
  token_free(tag_start, parser->allocator);
@@ -1186,7 +1270,7 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1186
1270
  is_self_closing = true;
1187
1271
  }
1188
1272
 
1189
- AST_HTML_OPEN_TAG_NODE_T* open_tag_node = ast_html_open_tag_node_init(
1273
+ return ast_html_open_tag_node_init(
1190
1274
  tag_start,
1191
1275
  tag_name,
1192
1276
  tag_end,
@@ -1197,37 +1281,50 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
1197
1281
  errors,
1198
1282
  parser->allocator
1199
1283
  );
1200
-
1201
- token_free(tag_start, parser->allocator);
1202
- token_free(tag_name, parser->allocator);
1203
- token_free(tag_end, parser->allocator);
1204
-
1205
- return open_tag_node;
1206
1284
  }
1207
1285
 
1208
1286
  static AST_HTML_CLOSE_TAG_NODE_T* parser_parse_html_close_tag(parser_T* parser) {
1209
- hb_array_T* errors = hb_array_init(8, parser->allocator);
1287
+ hb_array_T* errors = NULL;
1210
1288
  hb_array_T* children = hb_array_init(8, parser->allocator);
1211
1289
 
1212
- token_T* tag_opening = parser_consume_expected(parser, TOKEN_HTML_TAG_START_CLOSE, errors);
1290
+ token_T* tag_opening = parser_consume_expected(parser, TOKEN_HTML_TAG_START_CLOSE, &errors);
1213
1291
 
1214
1292
  parser_consume_whitespace(parser, children);
1215
1293
 
1216
- token_T* tag_name = parser_consume_expected(parser, TOKEN_IDENTIFIER, errors);
1294
+ token_T* tag_name = parser_consume_expected(parser, TOKEN_IDENTIFIER, &errors);
1217
1295
 
1218
- parser_consume_dot_notation_segments(parser, tag_name, errors);
1296
+ parser_consume_dot_notation_segments(parser, tag_name, &errors);
1297
+
1298
+ if (parser->svg_depth > 0 && hb_string_equals_case_insensitive(tag_name->value, hb_string("svg"))) {
1299
+ parser->svg_depth--;
1300
+ }
1219
1301
 
1220
1302
  parser_consume_whitespace(parser, children);
1221
1303
 
1222
1304
  token_T* tag_closing = parser_consume_if_present(parser, TOKEN_HTML_TAG_END);
1223
1305
 
1306
+ // </div/>
1307
+ if (tag_closing == NULL && token_is(parser, TOKEN_HTML_TAG_SELF_CLOSE) && tag_name != NULL) {
1308
+ tag_closing = parser_advance(parser);
1309
+
1310
+ append_end_tag_with_trailing_solidus_error(
1311
+ tag_name,
1312
+ tag_closing->location.start,
1313
+ tag_closing->location.end,
1314
+ parser->allocator,
1315
+ &errors,
1316
+ &parser->options
1317
+ );
1318
+ }
1319
+
1224
1320
  if (tag_closing == NULL) {
1225
1321
  append_unclosed_close_tag_error(
1226
1322
  tag_name,
1227
1323
  tag_opening->location.start,
1228
1324
  tag_name->location.end,
1229
1325
  parser->allocator,
1230
- errors
1326
+ &errors,
1327
+ &parser->options
1231
1328
  );
1232
1329
  }
1233
1330
 
@@ -1243,7 +1340,8 @@ static AST_HTML_CLOSE_TAG_NODE_T* parser_parse_html_close_tag(parser_T* parser)
1243
1340
  tag_opening->location.start,
1244
1341
  tag_closing->location.end,
1245
1342
  parser->allocator,
1246
- errors
1343
+ &errors,
1344
+ &parser->options
1247
1345
  );
1248
1346
 
1249
1347
  hb_allocator_dealloc(parser->allocator, expected.data);
@@ -1252,7 +1350,7 @@ static AST_HTML_CLOSE_TAG_NODE_T* parser_parse_html_close_tag(parser_T* parser)
1252
1350
 
1253
1351
  position_T end_position = tag_closing != NULL ? tag_closing->location.end : tag_name->location.end;
1254
1352
 
1255
- AST_HTML_CLOSE_TAG_NODE_T* close_tag = ast_html_close_tag_node_init(
1353
+ return ast_html_close_tag_node_init(
1256
1354
  tag_opening,
1257
1355
  tag_name,
1258
1356
  children,
@@ -1262,12 +1360,6 @@ static AST_HTML_CLOSE_TAG_NODE_T* parser_parse_html_close_tag(parser_T* parser)
1262
1360
  errors,
1263
1361
  parser->allocator
1264
1362
  );
1265
-
1266
- token_free(tag_opening, parser->allocator);
1267
- token_free(tag_name, parser->allocator);
1268
- token_free(tag_closing, parser->allocator);
1269
-
1270
- return close_tag;
1271
1363
  }
1272
1364
 
1273
1365
  // TODO: this should probably be AST_HTML_ELEMENT_NODE_T with a AST_HTML_SELF_CLOSING_TAG_NODE_T
@@ -1277,7 +1369,7 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_self_closing_element(
1277
1369
  ) {
1278
1370
  return ast_html_element_node_init(
1279
1371
  (AST_NODE_T*) open_tag,
1280
- open_tag->tag_name,
1372
+ token_copy(open_tag->tag_name, parser->allocator),
1281
1373
  NULL,
1282
1374
  NULL,
1283
1375
  true,
@@ -1293,28 +1385,30 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_regular_element(
1293
1385
  parser_T* parser,
1294
1386
  AST_HTML_OPEN_TAG_NODE_T* open_tag
1295
1387
  ) {
1296
- hb_array_T* errors = hb_array_init(8, parser->allocator);
1388
+ hb_array_T* errors = NULL;
1297
1389
  hb_array_T* body = hb_array_init(8, parser->allocator);
1298
1390
 
1299
1391
  parser_push_open_tag(parser, open_tag->tag_name);
1300
1392
 
1301
- if (!hb_string_is_empty(open_tag->tag_name->value) && parser_is_foreign_content_tag(open_tag->tag_name->value)) {
1302
- foreign_content_type_T content_type = parser_get_foreign_content_type(open_tag->tag_name->value);
1303
- parser_enter_foreign_content(parser, content_type);
1304
- parser_parse_foreign_content(parser, body, errors);
1393
+ if (parser_element_has_foreign_content(parser, open_tag->tag_name->value)) {
1394
+ hb_string_T tag_name = open_tag->tag_name->value;
1395
+ hb_string_T end_tag_name = parser_foreign_content_has_end_tag(tag_name) ? tag_name : HB_STRING_NULL;
1396
+
1397
+ parser_enter_foreign_content(parser, parser_get_foreign_content_kind(tag_name), end_tag_name);
1398
+ parser_parse_foreign_content(parser, body, &errors);
1305
1399
  } else {
1306
- parser_parse_in_data_state(parser, body, errors);
1400
+ parser_parse_in_data_state(parser, body, &errors);
1307
1401
  }
1308
1402
 
1309
1403
  if (!token_is(parser, TOKEN_HTML_TAG_START_CLOSE)) {
1310
- return parser_handle_missing_close_tag(parser, open_tag, body, errors);
1404
+ return parser_handle_missing_close_tag(parser, open_tag, body, &errors);
1311
1405
  }
1312
1406
 
1313
1407
  AST_HTML_CLOSE_TAG_NODE_T* close_tag = parser_parse_html_close_tag(parser);
1314
1408
 
1315
1409
  if (parser_in_svg_context(parser) == false && is_void_element(close_tag->tag_name->value)) {
1316
1410
  hb_array_push(body, close_tag);
1317
- parser_parse_in_data_state(parser, body, errors);
1411
+ parser_parse_in_data_state(parser, body, &errors);
1318
1412
  close_tag = parser_parse_html_close_tag(parser);
1319
1413
  }
1320
1414
 
@@ -1335,7 +1429,8 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_regular_element(
1335
1429
  unclosed->location.start,
1336
1430
  unclosed->location.end,
1337
1431
  parser->allocator,
1338
- errors
1432
+ &errors,
1433
+ &parser->options
1339
1434
  );
1340
1435
  token_free(unclosed, parser->allocator);
1341
1436
  }
@@ -1344,12 +1439,12 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_regular_element(
1344
1439
  token_T* popped_token = parser_pop_open_tag(parser);
1345
1440
  token_free(popped_token, parser->allocator);
1346
1441
  } else {
1347
- parser_handle_mismatched_tags(parser, close_tag, errors);
1442
+ parser_handle_mismatched_tags(parser, close_tag, &errors);
1348
1443
  }
1349
1444
 
1350
1445
  return ast_html_element_node_init(
1351
1446
  (AST_NODE_T*) open_tag,
1352
- open_tag->tag_name,
1447
+ token_copy(open_tag->tag_name, parser->allocator),
1353
1448
  body,
1354
1449
  (AST_NODE_T*) close_tag,
1355
1450
  false,
@@ -1369,12 +1464,14 @@ static AST_NODE_T* parser_parse_html_element(parser_T* parser) {
1369
1464
  // <tag />
1370
1465
  if (open_tag->is_void) { return (AST_NODE_T*) parser_parse_html_self_closing_element(parser, open_tag); }
1371
1466
 
1467
+ if (hb_string_equals_case_insensitive(open_tag->tag_name->value, hb_string("svg"))) { parser->svg_depth++; }
1468
+
1372
1469
  // <tag>, in void element list, and not in inside an <svg> element
1373
1470
  if (!open_tag->is_void && is_void_element(open_tag->tag_name->value) && !parser_in_svg_context(parser)) {
1374
1471
  return (AST_NODE_T*) parser_parse_html_self_closing_element(parser, open_tag);
1375
1472
  }
1376
1473
 
1377
- if (!hb_string_is_empty(open_tag->tag_name->value) && parser_is_foreign_content_tag(open_tag->tag_name->value)) {
1474
+ if (parser_element_has_foreign_content(parser, open_tag->tag_name->value)) {
1378
1475
  AST_HTML_ELEMENT_NODE_T* regular_element = parser_parse_html_regular_element(parser, open_tag);
1379
1476
 
1380
1477
  if (regular_element != NULL) { return (AST_NODE_T*) regular_element; }
@@ -1383,18 +1480,35 @@ static AST_NODE_T* parser_parse_html_element(parser_T* parser) {
1383
1480
  return (AST_NODE_T*) open_tag;
1384
1481
  }
1385
1482
 
1386
- static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser) {
1387
- hb_array_T* errors = hb_array_init(8, parser->allocator);
1483
+ static AST_NODE_T* parser_parse_erb_tag(parser_T* parser) {
1484
+ hb_array_T* errors = NULL;
1388
1485
 
1389
- token_T* opening_tag = parser_consume_expected(parser, TOKEN_ERB_START, errors);
1390
- token_T* content = parser_consume_expected(parser, TOKEN_ERB_CONTENT, errors);
1486
+ token_T* opening_tag = parser_consume_expected(parser, TOKEN_ERB_START, &errors);
1487
+ token_T* content = parser_consume_expected(parser, TOKEN_ERB_CONTENT, &errors);
1391
1488
 
1392
1489
  token_T* closing_tag = NULL;
1393
1490
  position_T end_position;
1394
1491
 
1395
1492
  if (token_is(parser, TOKEN_ERB_END)) {
1396
- closing_tag = parser_consume_expected(parser, TOKEN_ERB_END, errors);
1493
+ closing_tag = parser_consume_expected(parser, TOKEN_ERB_END, &errors);
1397
1494
  end_position = closing_tag->location.end;
1495
+
1496
+ bool is_well_formed = hb_string_equals(closing_tag->value, hb_string("%>"))
1497
+ || hb_string_equals(closing_tag->value, hb_string("-%>"))
1498
+ || hb_string_equals(closing_tag->value, hb_string("=%>"))
1499
+ || hb_string_equals(closing_tag->value, hb_string("%%>"));
1500
+
1501
+ if (!is_well_formed) {
1502
+ append_malformed_erb_closing_tag_error(
1503
+ opening_tag,
1504
+ closing_tag,
1505
+ opening_tag->location.start,
1506
+ closing_tag->location.end,
1507
+ parser->allocator,
1508
+ &errors,
1509
+ &parser->options
1510
+ );
1511
+ }
1398
1512
  } else if (token_is(parser, TOKEN_ERB_START)) {
1399
1513
  append_nested_erb_tag_error(
1400
1514
  opening_tag,
@@ -1403,7 +1517,8 @@ static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser) {
1403
1517
  parser->current_token->location.start,
1404
1518
  parser->current_token->location.end,
1405
1519
  parser->allocator,
1406
- errors
1520
+ &errors,
1521
+ &parser->options
1407
1522
  );
1408
1523
  end_position = parser->current_token->location.start;
1409
1524
  } else {
@@ -1412,12 +1527,25 @@ static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser) {
1412
1527
  opening_tag->location.start,
1413
1528
  parser->current_token->location.start,
1414
1529
  parser->allocator,
1415
- errors
1530
+ &errors,
1531
+ &parser->options
1416
1532
  );
1417
1533
  end_position = parser->current_token->location.start;
1418
1534
  }
1419
1535
 
1420
- AST_ERB_CONTENT_NODE_T* erb_node = ast_erb_content_node_init(
1536
+ if (hb_string_equals(opening_tag->value, hb_string("<%#"))) {
1537
+ return (AST_NODE_T*) ast_erb_comment_node_init(
1538
+ opening_tag,
1539
+ content,
1540
+ closing_tag,
1541
+ opening_tag->location.start,
1542
+ end_position,
1543
+ errors,
1544
+ parser->allocator
1545
+ );
1546
+ }
1547
+
1548
+ return (AST_NODE_T*) ast_erb_content_node_init(
1421
1549
  opening_tag,
1422
1550
  content,
1423
1551
  closing_tag,
@@ -1430,26 +1558,103 @@ static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser) {
1430
1558
  errors,
1431
1559
  parser->allocator
1432
1560
  );
1561
+ }
1433
1562
 
1434
- token_free(opening_tag, parser->allocator);
1435
- token_free(content, parser->allocator);
1436
- if (closing_tag != NULL) { token_free(closing_tag, parser->allocator); }
1563
+ static bool is_tag_name_character(char character) {
1564
+ return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')
1565
+ || (character >= '0' && character <= '9') || character == '-';
1566
+ }
1567
+
1568
+ static bool parser_scan_for_end_tag(const parser_T* parser, hb_string_T tag_name) {
1569
+ hb_string_T source = parser->lexer->source;
1570
+
1571
+ if (hb_string_is_empty(source) || hb_string_is_empty(tag_name)) { return false; }
1572
+
1573
+ for (uint32_t index = parser->current_token->range.from; index + 2 < source.length; index++) {
1574
+ if (source.data[index] != '<' || source.data[index + 1] != '/') { continue; }
1575
+
1576
+ uint32_t name_start = index + 2;
1437
1577
 
1438
- return erb_node;
1578
+ if (name_start + tag_name.length > source.length) { return false; }
1579
+
1580
+ hb_string_T candidate = { .data = source.data + name_start, .length = tag_name.length };
1581
+
1582
+ if (!hb_string_equals_case_insensitive(candidate, tag_name)) { continue; }
1583
+
1584
+ uint32_t after = name_start + tag_name.length;
1585
+
1586
+ if (after >= source.length || !is_tag_name_character(source.data[after])) { return true; }
1587
+ }
1588
+
1589
+ return false;
1590
+ }
1591
+
1592
+ static bool parser_foreign_content_end_tag_ahead(parser_T* parser, hb_string_T tag_name) {
1593
+ int index = parser_foreign_content_element_index(tag_name);
1594
+ uint32_t position = parser->current_token->range.from;
1595
+
1596
+ if (index >= 0 && position >= parser->foreign_content_absent_from[index]) { return false; }
1597
+
1598
+ if (parser_scan_for_end_tag(parser, tag_name)) { return true; }
1599
+
1600
+ if (index >= 0 && position < parser->foreign_content_absent_from[index]) {
1601
+ parser->foreign_content_absent_from[index] = position;
1602
+ }
1603
+
1604
+ return false;
1605
+ }
1606
+
1607
+ // <title> and <plaintext> are ordinary elements in XML documents, <title> also inside <svg>
1608
+ static bool parser_element_has_foreign_content(parser_T* parser, hb_string_T tag_name) {
1609
+ if (parser_get_foreign_content_kind(tag_name) == FOREIGN_CONTENT_NONE) { return false; }
1610
+ if (parser_foreign_content_is_html_only(tag_name) && parser->xml_document) { return false; }
1611
+ if (hb_string_equals_case_insensitive(tag_name, hb_string("title")) && parser_in_svg_context(parser)) {
1612
+ return false;
1613
+ }
1614
+
1615
+ if (parser_foreign_content_has_end_tag(tag_name) && !parser_foreign_content_end_tag_ahead(parser, tag_name)) {
1616
+ return false;
1617
+ }
1618
+
1619
+ return true;
1439
1620
  }
1440
1621
 
1441
- static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_array_T* errors) {
1622
+ // <textarea> and <title> hold text, <script> and <style> hold raw source
1623
+ static void parser_append_foreign_content_from_buffer(
1624
+ const parser_T* parser,
1625
+ hb_buffer_T* buffer,
1626
+ hb_array_T* children,
1627
+ position_T start
1628
+ ) {
1629
+ bool is_text = parser->foreign_content_kind == FOREIGN_CONTENT_RCDATA;
1630
+
1631
+ if (!is_text || buffer->length == 0) {
1632
+ parser_append_literal_node_from_buffer(parser, buffer, children, start);
1633
+ return;
1634
+ }
1635
+
1636
+ hb_string_T content = { .data = buffer->value, .length = (uint32_t) buffer->length };
1637
+
1638
+ hb_array_append(
1639
+ children,
1640
+ ast_html_text_node_init(content, start, parser->current_token->location.start, NULL, parser->allocator)
1641
+ );
1642
+
1643
+ hb_buffer_free(buffer);
1644
+ hb_buffer_init(buffer, 128, parser->allocator);
1645
+ }
1646
+
1647
+ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_array_T** errors) {
1442
1648
  hb_buffer_T content;
1443
1649
  hb_buffer_init(&content, 1024, parser->allocator);
1444
1650
  position_T start = parser->current_token->location.start;
1445
- hb_string_T expected_closing_tag = parser_get_foreign_content_closing_tag(parser->foreign_content_type);
1446
- bool has_closing_tag = !hb_string_is_empty(expected_closing_tag);
1651
+ bool has_closing_tag = !hb_string_is_empty(parser->foreign_content_tag_name);
1447
1652
 
1448
1653
  while (!token_is(parser, TOKEN_EOF)) {
1449
1654
  if (token_is(parser, TOKEN_ERB_START)) {
1450
- parser_append_literal_node_from_buffer(parser, &content, children, start);
1655
+ parser_append_foreign_content_from_buffer(parser, &content, children, start);
1451
1656
 
1452
- AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser);
1657
+ AST_NODE_T* erb_node = parser_parse_erb_tag(parser);
1453
1658
  hb_array_append(children, erb_node);
1454
1659
 
1455
1660
  start = parser->current_token->location.start;
@@ -1464,7 +1669,7 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children,
1464
1669
  bool is_potential_match = false;
1465
1670
 
1466
1671
  if (next_token && next_token->type == TOKEN_IDENTIFIER && !hb_string_is_empty(next_token->value)) {
1467
- is_potential_match = parser_is_expected_closing_tag_name(next_token->value, parser->foreign_content_type);
1672
+ is_potential_match = parser_is_foreign_content_closing_tag_name(parser, next_token->value);
1468
1673
  }
1469
1674
 
1470
1675
  lexer_restore_state(parser->lexer, saved_state);
@@ -1472,7 +1677,7 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children,
1472
1677
  if (next_token) { token_free(next_token, parser->allocator); }
1473
1678
 
1474
1679
  if (is_potential_match) {
1475
- parser_append_literal_node_from_buffer(parser, &content, children, start);
1680
+ parser_append_foreign_content_from_buffer(parser, &content, children, start);
1476
1681
  parser_exit_foreign_content(parser);
1477
1682
 
1478
1683
  hb_buffer_free(&content);
@@ -1486,18 +1691,19 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children,
1486
1691
  token_free(token, parser->allocator);
1487
1692
  }
1488
1693
 
1489
- parser_append_literal_node_from_buffer(parser, &content, children, start);
1694
+ parser_append_foreign_content_from_buffer(parser, &content, children, start);
1490
1695
  parser_exit_foreign_content(parser);
1491
1696
  hb_buffer_free(&content);
1492
1697
  }
1493
1698
 
1494
- static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T* errors) {
1699
+ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T** errors) {
1495
1700
  if (!parser->options.html) {
1496
1701
  parser_parse_foreign_content(parser, children, errors);
1497
1702
  return;
1498
1703
  }
1499
1704
 
1500
1705
  while (token_is_not(parser, TOKEN_EOF)) {
1706
+ if (parser_options_past_deadline(&parser->options)) { break; }
1501
1707
 
1502
1708
  if (token_is(parser, TOKEN_ERB_START)) {
1503
1709
  hb_array_append(children, parser_parse_erb_tag(parser));
@@ -1517,6 +1723,12 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h
1517
1723
  continue;
1518
1724
  }
1519
1725
 
1726
+ if (token_is(parser, TOKEN_XML_PROCESSING_INSTRUCTION_START)) {
1727
+ hb_array_append(children, parser_parse_xml_processing_instruction(parser));
1728
+ parser->consecutive_error_count = 0;
1729
+ continue;
1730
+ }
1731
+
1520
1732
  if (token_is(parser, TOKEN_CDATA_START)) {
1521
1733
  hb_array_append(children, parser_parse_cdata(parser));
1522
1734
  parser->consecutive_error_count = 0;
@@ -1601,11 +1813,18 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h
1601
1813
  }
1602
1814
  }
1603
1815
 
1604
- static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_string_T tag_name) {
1816
+ static size_t find_matching_close_tag(
1817
+ hb_array_T* nodes,
1818
+ size_t start_index,
1819
+ hb_string_T tag_name,
1820
+ const parser_options_T* options
1821
+ ) {
1605
1822
  int depth = 0;
1606
1823
 
1607
- for (size_t i = start_idx + 1; i < hb_array_size(nodes); i++) {
1608
- AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1824
+ for (size_t index = start_index + 1; index < hb_array_size(nodes); index++) {
1825
+ if (parser_options_past_deadline(options)) { return (size_t) -1; }
1826
+
1827
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
1609
1828
  if (node == NULL) { continue; }
1610
1829
 
1611
1830
  if (node->type == AST_HTML_OPEN_TAG_NODE) {
@@ -1616,7 +1835,7 @@ static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_st
1616
1835
  AST_HTML_CLOSE_TAG_NODE_T* close = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1617
1836
 
1618
1837
  if (hb_string_equals_case_insensitive(close->tag_name->value, tag_name)) {
1619
- if (depth == 0) { return i; }
1838
+ if (depth == 0) { return index; }
1620
1839
  depth--;
1621
1840
  }
1622
1841
  }
@@ -1625,23 +1844,23 @@ static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_st
1625
1844
  return (size_t) -1;
1626
1845
  }
1627
1846
 
1628
- static size_t find_implicit_close_index(hb_array_T* nodes, size_t start_idx, hb_string_T tag_name) {
1847
+ static size_t find_implicit_close_index(hb_array_T* nodes, size_t start_index, hb_string_T tag_name) {
1629
1848
  if (!has_optional_end_tag(tag_name)) { return (size_t) -1; }
1630
1849
 
1631
- for (size_t i = start_idx + 1; i < hb_array_size(nodes); i++) {
1632
- AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1850
+ for (size_t index = start_index + 1; index < hb_array_size(nodes); index++) {
1851
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
1633
1852
  if (node == NULL) { continue; }
1634
1853
 
1635
1854
  if (node->type == AST_HTML_OPEN_TAG_NODE) {
1636
1855
  AST_HTML_OPEN_TAG_NODE_T* open = (AST_HTML_OPEN_TAG_NODE_T*) node;
1637
1856
  hb_string_T next_tag_name = open->tag_name->value;
1638
1857
 
1639
- if (should_implicitly_close(tag_name, next_tag_name)) { return i; }
1858
+ if (should_implicitly_close(tag_name, next_tag_name)) { return index; }
1640
1859
  } else if (node->type == AST_HTML_CLOSE_TAG_NODE) {
1641
1860
  AST_HTML_CLOSE_TAG_NODE_T* close = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1642
1861
  hb_string_T close_tag_name = close->tag_name->value;
1643
1862
 
1644
- if (parent_closes_element(tag_name, close_tag_name)) { return i; }
1863
+ if (parent_closes_element(tag_name, close_tag_name)) { return index; }
1645
1864
  }
1646
1865
  }
1647
1866
 
@@ -1655,6 +1874,46 @@ static hb_array_T* parser_build_elements_from_tags(
1655
1874
  hb_allocator_T* allocator
1656
1875
  );
1657
1876
 
1877
+ static bool has_close_tag_for_name(hb_array_T* close_tag_names, hb_string_T tag_name) {
1878
+ for (size_t i = 0; i < hb_array_size(close_tag_names); i++) {
1879
+ hb_string_T* name = (hb_string_T*) hb_array_get(close_tag_names, i);
1880
+ if (name != NULL && hb_string_equals_case_insensitive(*name, tag_name)) { return true; }
1881
+ }
1882
+
1883
+ return false;
1884
+ }
1885
+
1886
+ static hb_array_T* collect_close_tag_names(hb_array_T* nodes, hb_allocator_T* allocator) {
1887
+ hb_array_T* close_tag_names = hb_array_init(16, allocator);
1888
+
1889
+ for (size_t i = 0; i < hb_array_size(nodes); i++) {
1890
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1891
+ if (node == NULL || node->type != AST_HTML_CLOSE_TAG_NODE) { continue; }
1892
+
1893
+ AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1894
+ hb_string_T name = close_tag->tag_name->value;
1895
+
1896
+ if (!has_close_tag_for_name(close_tag_names, name)) {
1897
+ hb_string_T* stored = hb_allocator_alloc(allocator, sizeof(hb_string_T));
1898
+ *stored = name;
1899
+ hb_array_append(close_tag_names, stored);
1900
+ }
1901
+ }
1902
+
1903
+ return close_tag_names;
1904
+ }
1905
+
1906
+ static void free_close_tag_names(hb_array_T** close_tag_names, hb_allocator_T* allocator) {
1907
+ if (close_tag_names == NULL || *close_tag_names == NULL) { return; }
1908
+
1909
+ for (size_t i = 0; i < hb_array_size(*close_tag_names); i++) {
1910
+ hb_string_T* name = (hb_string_T*) hb_array_get(*close_tag_names, i);
1911
+ if (name != NULL) { hb_allocator_dealloc(allocator, name); }
1912
+ }
1913
+
1914
+ hb_array_free(close_tag_names);
1915
+ }
1916
+
1658
1917
  static hb_array_T* parser_build_elements_from_tags(
1659
1918
  hb_array_T* nodes,
1660
1919
  hb_array_T* errors,
@@ -1663,8 +1922,11 @@ static hb_array_T* parser_build_elements_from_tags(
1663
1922
  ) {
1664
1923
  bool strict = options ? options->strict : false;
1665
1924
  hb_array_T* result = hb_array_init(hb_array_size(nodes), allocator);
1925
+ hb_array_T* close_tag_names = collect_close_tag_names(nodes, allocator);
1666
1926
 
1667
1927
  for (size_t index = 0; index < hb_array_size(nodes); index++) {
1928
+ if (parser_options_past_deadline(options)) { break; }
1929
+
1668
1930
  AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
1669
1931
  if (node == NULL) { continue; }
1670
1932
 
@@ -1672,7 +1934,11 @@ static hb_array_T* parser_build_elements_from_tags(
1672
1934
  AST_HTML_OPEN_TAG_NODE_T* open_tag = (AST_HTML_OPEN_TAG_NODE_T*) node;
1673
1935
  hb_string_T tag_name = open_tag->tag_name->value;
1674
1936
 
1675
- size_t close_index = find_matching_close_tag(nodes, index, tag_name);
1937
+ size_t close_index = (size_t) -1;
1938
+
1939
+ if (has_close_tag_for_name(close_tag_names, tag_name)) {
1940
+ close_index = find_matching_close_tag(nodes, index, tag_name, options);
1941
+ }
1676
1942
 
1677
1943
  if (close_index == (size_t) -1) {
1678
1944
  size_t implicit_close_index = find_implicit_close_index(nodes, index, tag_name);
@@ -1694,7 +1960,7 @@ static hb_array_T* parser_build_elements_from_tags(
1694
1960
  if (last_body_node != NULL) { end_position = last_body_node->location.end; }
1695
1961
  }
1696
1962
 
1697
- hb_array_T* element_errors = hb_array_init(8, allocator);
1963
+ hb_array_T* element_errors = NULL;
1698
1964
 
1699
1965
  if (strict) {
1700
1966
  append_omitted_closing_tag_error(
@@ -1703,21 +1969,22 @@ static hb_array_T* parser_build_elements_from_tags(
1703
1969
  open_tag->base.location.start,
1704
1970
  open_tag->base.location.end,
1705
1971
  allocator,
1706
- element_errors
1972
+ &element_errors,
1973
+ options
1707
1974
  );
1708
1975
  }
1709
1976
 
1710
1977
  AST_HTML_OMITTED_CLOSE_TAG_NODE_T* omitted_close_tag = ast_html_omitted_close_tag_node_init(
1711
- open_tag->tag_name,
1978
+ token_copy(open_tag->tag_name, allocator),
1712
1979
  end_position,
1713
1980
  end_position,
1714
- hb_array_init(8, allocator),
1981
+ NULL,
1715
1982
  allocator
1716
1983
  );
1717
1984
 
1718
1985
  AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
1719
1986
  (AST_NODE_T*) open_tag,
1720
- open_tag->tag_name,
1987
+ token_copy(open_tag->tag_name, allocator),
1721
1988
  processed_body,
1722
1989
  (AST_NODE_T*) omitted_close_tag,
1723
1990
  false,
@@ -1738,7 +2005,8 @@ static hb_array_T* parser_build_elements_from_tags(
1738
2005
  open_tag->base.location.start,
1739
2006
  open_tag->base.location.end,
1740
2007
  allocator,
1741
- open_tag->base.errors
2008
+ &open_tag->base.errors,
2009
+ options
1742
2010
  );
1743
2011
  }
1744
2012
 
@@ -1756,11 +2024,11 @@ static hb_array_T* parser_build_elements_from_tags(
1756
2024
  hb_array_T* processed_body = parser_build_elements_from_tags(body, errors, options, allocator);
1757
2025
  hb_array_free(&body);
1758
2026
 
1759
- hb_array_T* element_errors = hb_array_init(8, allocator);
2027
+ hb_array_T* element_errors = NULL;
1760
2028
 
1761
2029
  AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
1762
2030
  (AST_NODE_T*) open_tag,
1763
- open_tag->tag_name,
2031
+ token_copy(open_tag->tag_name, allocator),
1764
2032
  processed_body,
1765
2033
  (AST_NODE_T*) close_tag,
1766
2034
  false,
@@ -1785,7 +2053,8 @@ static hb_array_T* parser_build_elements_from_tags(
1785
2053
  close_tag->base.location.start,
1786
2054
  close_tag->base.location.end,
1787
2055
  allocator,
1788
- close_tag->base.errors
2056
+ &close_tag->base.errors,
2057
+ options
1789
2058
  );
1790
2059
  }
1791
2060
  }
@@ -1796,17 +2065,19 @@ static hb_array_T* parser_build_elements_from_tags(
1796
2065
  }
1797
2066
  }
1798
2067
 
2068
+ free_close_tag_names(&close_tag_names, allocator);
2069
+
1799
2070
  return result;
1800
2071
  }
1801
2072
 
1802
2073
  static AST_DOCUMENT_NODE_T* parser_parse_document(parser_T* parser) {
1803
2074
  hb_array_T* children = hb_array_init(8, parser->allocator);
1804
- hb_array_T* errors = hb_array_init(8, parser->allocator);
2075
+ hb_array_T* errors = NULL;
1805
2076
  position_T start = parser->current_token->location.start;
1806
2077
 
1807
- parser_parse_in_data_state(parser, children, errors);
2078
+ parser_parse_in_data_state(parser, children, &errors);
1808
2079
 
1809
- token_T* eof = parser_consume_expected(parser, TOKEN_EOF, errors);
2080
+ token_T* eof = parser_consume_expected(parser, TOKEN_EOF, &errors);
1810
2081
 
1811
2082
  AST_DOCUMENT_NODE_T* document_node =
1812
2083
  ast_document_node_init(children, NULL, HERB_PRISM_NODE_EMPTY, start, eof->location.end, errors, parser->allocator);
@@ -1822,7 +2093,7 @@ AST_DOCUMENT_NODE_T* herb_parser_parse(parser_T* parser) {
1822
2093
 
1823
2094
  static void parser_handle_whitespace(parser_T* parser, token_T* whitespace_token, hb_array_T* children) {
1824
2095
  if (parser->options.track_whitespace) {
1825
- hb_array_T* errors = hb_array_init(8, parser->allocator);
2096
+ hb_array_T* errors = NULL;
1826
2097
  AST_WHITESPACE_NODE_T* whitespace_node = ast_whitespace_node_init(
1827
2098
  whitespace_token,
1828
2099
  whitespace_token->location.start,
@@ -1831,6 +2102,8 @@ static void parser_handle_whitespace(parser_T* parser, token_T* whitespace_token
1831
2102
  parser->allocator
1832
2103
  );
1833
2104
  hb_array_append(children, whitespace_node);
2105
+
2106
+ return;
1834
2107
  }
1835
2108
 
1836
2109
  token_free(whitespace_token, parser->allocator);