herb 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -2
  3. data/config.yml +6 -0
  4. data/ext/herb/extconf.rb +8 -31
  5. data/ext/herb/nodes.c +75 -75
  6. data/ext/herb/parser_options_helpers.c +26 -0
  7. data/lib/herb/analysis/partial_index.rb +1 -1
  8. data/lib/herb/analysis/partial_resolution.rb +27 -0
  9. data/lib/herb/ast/helpers.rb +1 -1
  10. data/lib/herb/configuration.rb +11 -1
  11. data/lib/herb/dev/classifier.rb +1 -1
  12. data/lib/herb/engine/compiler.rb +5 -8
  13. data/lib/herb/engine/slots/dynamics_compiler.rb +53 -14
  14. data/lib/herb/engine/slots/subtree_compiler.rb +1 -1
  15. data/lib/herb/engine/slots/visitor.rb +32 -4
  16. data/lib/herb/engine/visitors/instrumentation_visitor.rb +18 -6
  17. data/lib/herb/parser_options.rb +6 -3
  18. data/lib/herb/project.rb +19 -8
  19. data/lib/herb/version.rb +1 -1
  20. data/lib/herb.rb +1 -1
  21. data/sig/herb/analysis/partial_resolution.rbs +8 -0
  22. data/sig/herb/configuration.rbs +3 -0
  23. data/sig/herb/engine/slots/dynamics_compiler.rbs +24 -7
  24. data/sig/herb/engine/slots/visitor.rbs +14 -0
  25. data/sig/herb/engine/visitors/instrumentation_visitor.rbs +5 -1
  26. data/sig/herb/parser_options.rbs +6 -4
  27. data/sig/herb.rbs +2 -2
  28. data/src/analyze/action_view/attribute_extraction_helpers.c +40 -21
  29. data/src/analyze/action_view/generated_handlers.c +36 -36
  30. data/src/analyze/action_view/image_tag.c +7 -3
  31. data/src/analyze/action_view/javascript_include_tag.c +6 -3
  32. data/src/analyze/action_view/link_to.c +23 -23
  33. data/src/analyze/action_view/registry.c +7 -3
  34. data/src/analyze/action_view/tag_helper_node_builders.c +6 -13
  35. data/src/analyze/action_view/tag_helpers.c +139 -90
  36. data/src/analyze/action_view/turbo_frame_tag.c +5 -4
  37. data/src/analyze/analyze.c +35 -19
  38. data/src/analyze/analyze_helpers.c +161 -188
  39. data/src/analyze/analyzed_ruby.c +8 -7
  40. data/src/analyze/control_type.c +29 -22
  41. data/src/analyze/herb_directives.c +16 -20
  42. data/src/analyze/iteration_nodes.c +69 -24
  43. data/src/analyze/parse_errors.c +32 -22
  44. data/src/analyze/postfix_conditionals.c +23 -13
  45. data/src/analyze/prism_annotate.c +29 -28
  46. data/src/analyze/render_nodes.c +51 -41
  47. data/src/analyze/strict_locals.c +14 -15
  48. data/src/analyze/ternary_conditionals.c +14 -10
  49. data/src/herb.c +10 -8
  50. data/src/include/analyze/action_view/tag_helper_handler.h +1 -1
  51. data/src/include/analyze/action_view/tag_helper_node_builders.h +1 -0
  52. data/src/include/analyze/action_view/tag_helpers.h +1 -1
  53. data/src/include/analyze/analyze.h +3 -2
  54. data/src/include/analyze/analyzed_ruby.h +4 -2
  55. data/src/include/analyze/helpers.h +6 -6
  56. data/src/include/herb.h +3 -2
  57. data/src/include/parser/parser_options.h +5 -0
  58. data/src/include/prism/prism_context.h +12 -12
  59. data/src/include/prism/prism_helpers.h +30 -0
  60. data/src/include/prism_version.h +11 -0
  61. data/src/include/version.h +1 -1
  62. data/src/parser/parser_helpers.c +1 -2
  63. data/src/parser.c +6 -7
  64. data/src/parser_options_defaults.c +1 -0
  65. data/src/prism/prism_helpers.c +84 -50
  66. data/src/prism/ruby_parser.c +20 -20
  67. data/templates/ext/herb/nodes.c.erb +5 -5
  68. data/templates/ext/herb/parser_options_helpers.c.erb +33 -0
  69. data/templates/java/nodes.c.erb +6 -6
  70. data/templates/java/parser_options_helpers.c.erb +41 -2
  71. data/templates/java/parser_options_helpers.h.erb +2 -1
  72. data/templates/javascript/packages/node/extension/nodes.cpp.erb +5 -5
  73. data/templates/javascript/packages/node/extension/parser_options_helpers.cpp.erb +34 -2
  74. data/templates/javascript/packages/node/extension/parser_options_helpers.h.erb +1 -1
  75. data/templates/lib/herb/parser_options.rb.erb +1 -1
  76. data/templates/rust/src/prism/generated_deserialize.rs.erb +90 -44
  77. data/templates/rust/src/prism/generated_prettyprint.rs.erb +18 -27
  78. data/templates/src/analyze/action_view/generated_handlers.c.erb +23 -22
  79. data/templates/src/include/parser/parser_options.h.erb +10 -0
  80. data/templates/src/include/prism_version.h.erb +17 -0
  81. data/templates/src/parser_options_defaults.c.erb +1 -1
  82. data/templates/template.rb +17 -2
  83. data/templates/wasm/nodes.cpp.erb +5 -5
  84. data/templates/wasm/parser_options_helpers.cpp.erb +24 -0
  85. data/vendor/prism/Rakefile +76 -7
  86. data/vendor/prism/config.yml +174 -161
  87. data/vendor/prism/include/prism/arena.h +45 -0
  88. data/vendor/prism/include/prism/arena.hpp +42 -0
  89. data/vendor/prism/include/prism/ast.h +2738 -611
  90. data/vendor/prism/include/prism/ast.hpp +641 -0
  91. data/vendor/prism/include/prism/buffer.h +60 -0
  92. data/vendor/prism/include/prism/buffer.hpp +55 -0
  93. data/vendor/prism/include/prism/comments.h +72 -0
  94. data/vendor/prism/include/prism/compiler/accel.h +26 -0
  95. data/vendor/prism/include/prism/compiler/align.h +36 -0
  96. data/vendor/prism/include/prism/compiler/assume.h +25 -0
  97. data/vendor/prism/include/prism/compiler/branch.h +26 -0
  98. data/vendor/prism/include/prism/compiler/bswap.h +46 -0
  99. data/vendor/prism/include/prism/compiler/cplusplus.hpp +16 -0
  100. data/vendor/prism/include/prism/compiler/endianness.h +34 -0
  101. data/vendor/prism/include/prism/compiler/exceptions.hpp +17 -0
  102. data/vendor/prism/include/prism/compiler/exported.h +24 -0
  103. data/vendor/prism/include/prism/compiler/fallthrough.h +22 -0
  104. data/vendor/prism/include/prism/compiler/filesystem.h +32 -0
  105. data/vendor/prism/include/prism/compiler/flex_array.h +19 -0
  106. data/vendor/prism/include/prism/compiler/force_inline.h +21 -0
  107. data/vendor/prism/include/prism/compiler/format.h +27 -0
  108. data/vendor/prism/include/prism/compiler/inline.h +17 -0
  109. data/vendor/prism/include/prism/compiler/lifetimebound.hpp +25 -0
  110. data/vendor/prism/include/prism/compiler/nodiscard.h +22 -0
  111. data/vendor/prism/include/prism/compiler/nonnull.h +18 -0
  112. data/vendor/prism/include/prism/compiler/unused.h +18 -0
  113. data/vendor/prism/include/prism/constant_pool.h +106 -0
  114. data/vendor/prism/include/prism/diagnostic.h +55 -402
  115. data/vendor/prism/include/prism/errors_format.h +61 -0
  116. data/vendor/prism/include/prism/excludes.h +29 -0
  117. data/vendor/prism/include/prism/integer.h +41 -0
  118. data/vendor/prism/include/prism/internal/allocator.h +68 -0
  119. data/vendor/prism/include/prism/internal/allocator_debug.h +88 -0
  120. data/vendor/prism/include/prism/internal/arena.h +108 -0
  121. data/vendor/prism/include/prism/internal/bit.h +42 -0
  122. data/vendor/prism/include/prism/internal/buffer.h +94 -0
  123. data/vendor/prism/include/prism/internal/char.h +145 -0
  124. data/vendor/prism/include/prism/internal/comments.h +54 -0
  125. data/vendor/prism/include/prism/internal/constant_pool.h +152 -0
  126. data/vendor/prism/include/prism/internal/diagnostic.h +389 -0
  127. data/vendor/prism/include/prism/{encoding.h → internal/encoding.h} +27 -68
  128. data/vendor/prism/include/prism/internal/integer.h +68 -0
  129. data/vendor/prism/include/prism/internal/isinf.h +16 -0
  130. data/vendor/prism/include/prism/internal/line_offset_list.h +34 -0
  131. data/vendor/prism/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  132. data/vendor/prism/include/prism/internal/magic_comments.h +23 -0
  133. data/vendor/prism/include/prism/internal/memchr.h +15 -0
  134. data/vendor/prism/include/prism/internal/node.h +42 -0
  135. data/vendor/prism/include/prism/internal/options.h +232 -0
  136. data/vendor/prism/include/prism/internal/parser.h +1015 -0
  137. data/vendor/prism/include/prism/internal/regexp.h +26 -0
  138. data/vendor/prism/include/prism/internal/serialize.h +39 -0
  139. data/vendor/prism/include/prism/internal/source.h +72 -0
  140. data/vendor/prism/include/prism/internal/static_literals.h +60 -0
  141. data/vendor/prism/include/prism/internal/stringy.h +30 -0
  142. data/vendor/prism/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  143. data/vendor/prism/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  144. data/vendor/prism/include/prism/internal/tokens.h +11 -0
  145. data/vendor/prism/include/prism/internal/xxh3.h +16 -0
  146. data/vendor/prism/include/prism/json.h +40 -0
  147. data/vendor/prism/include/prism/line_offset_list.h +69 -0
  148. data/vendor/prism/include/prism/magic_comments.h +53 -0
  149. data/vendor/prism/include/prism/node.h +59 -49
  150. data/vendor/prism/include/prism/node.hpp +272 -0
  151. data/vendor/prism/include/prism/node_type.hpp +38 -0
  152. data/vendor/prism/include/prism/options.h +154 -279
  153. data/vendor/prism/include/prism/options.hpp +43 -0
  154. data/vendor/prism/include/prism/parser.h +367 -870
  155. data/vendor/prism/include/prism/parser.hpp +274 -0
  156. data/vendor/prism/include/prism/prettyprint.h +14 -9
  157. data/vendor/prism/include/prism/serialize.h +123 -0
  158. data/vendor/prism/include/prism/source.h +163 -0
  159. data/vendor/prism/include/prism/stream.h +36 -0
  160. data/vendor/prism/include/prism/string_query.h +82 -0
  161. data/vendor/prism/include/prism/stringy.h +61 -0
  162. data/vendor/prism/include/prism/stringy.hpp +31 -0
  163. data/vendor/prism/include/prism/version.h +20 -3
  164. data/vendor/prism/include/prism.h +35 -320
  165. data/vendor/prism/include/prism.hpp +24 -0
  166. data/vendor/prism/src/arena.c +122 -0
  167. data/vendor/prism/src/{util/pm_buffer.c → buffer.c} +53 -21
  168. data/vendor/prism/src/{util/pm_char.c → char.c} +28 -57
  169. data/vendor/prism/src/comments.c +455 -0
  170. data/vendor/prism/src/constant_pool.c +406 -0
  171. data/vendor/prism/src/diagnostic.c +120 -68
  172. data/vendor/prism/src/encoding.c +8 -3
  173. data/vendor/prism/src/errors_format.c +519 -0
  174. data/vendor/prism/src/{util/pm_integer.c → integer.c} +38 -22
  175. data/vendor/prism/src/json.c +5733 -0
  176. data/vendor/prism/src/line_offset_list.c +100 -0
  177. data/vendor/prism/src/list.c +24 -0
  178. data/vendor/prism/src/{util/pm_memchr.c → memchr.c} +8 -6
  179. data/vendor/prism/src/node.c +3465 -7787
  180. data/vendor/prism/src/options.c +161 -54
  181. data/vendor/prism/src/parser.c +371 -0
  182. data/vendor/prism/src/prettyprint.c +421 -354
  183. data/vendor/prism/src/prism.c +8522 -7280
  184. data/vendor/prism/src/regexp.c +960 -45
  185. data/vendor/prism/src/serialize.c +2434 -2048
  186. data/vendor/prism/src/source.c +524 -0
  187. data/vendor/prism/src/static_literals.c +303 -360
  188. data/vendor/prism/src/string_query.c +180 -0
  189. data/vendor/prism/src/stringy.c +61 -0
  190. data/vendor/prism/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  191. data/vendor/prism/src/strpbrk.c +447 -0
  192. data/vendor/prism/src/{token_type.c → tokens.c} +39 -18
  193. data/vendor/prism/src/xxh3.c +292 -0
  194. data/vendor/prism/templates/ext/prism/api_node.c.erb +58 -44
  195. data/vendor/prism/templates/include/prism/ast.h.erb +67 -17
  196. data/vendor/prism/templates/include/prism/ast.hpp.erb +31 -0
  197. data/vendor/prism/templates/include/prism/internal/diagnostic.h.erb +60 -0
  198. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/AbstractNodeVisitor.java.erb +1 -1
  199. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Loader.java.erb +232 -133
  200. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Nodes.java.erb +62 -23
  201. data/vendor/prism/templates/javascript/src/deserialize.js.erb +153 -146
  202. data/vendor/prism/templates/javascript/src/nodes.js.erb +4 -9
  203. data/vendor/prism/templates/lib/prism/compiler.rb.erb +14 -5
  204. data/vendor/prism/templates/lib/prism/dispatcher.rb.erb +20 -12
  205. data/vendor/prism/templates/lib/prism/dot_visitor.rb.erb +37 -11
  206. data/vendor/prism/templates/lib/prism/dsl.rb.erb +43 -4
  207. data/vendor/prism/templates/lib/prism/inspect_visitor.rb.erb +26 -10
  208. data/vendor/prism/templates/lib/prism/mutation_compiler.rb.erb +5 -2
  209. data/vendor/prism/templates/lib/prism/node.rb.erb +283 -105
  210. data/vendor/prism/templates/lib/prism/reflection.rb.erb +11 -2
  211. data/vendor/prism/templates/lib/prism/serialize.rb.erb +291 -134
  212. data/vendor/prism/templates/lib/prism/visitor.rb.erb +18 -0
  213. data/vendor/prism/templates/src/diagnostic.c.erb +114 -63
  214. data/vendor/prism/templates/src/json.c.erb +130 -0
  215. data/vendor/prism/templates/src/node.c.erb +79 -236
  216. data/vendor/prism/templates/src/prettyprint.c.erb +24 -13
  217. data/vendor/prism/templates/src/serialize.c.erb +369 -183
  218. data/vendor/prism/templates/src/{token_type.c.erb → tokens.c.erb} +22 -11
  219. data/vendor/prism/templates/template.rb +193 -91
  220. metadata +100 -42
  221. data/vendor/prism/include/prism/defines.h +0 -260
  222. data/vendor/prism/include/prism/pack.h +0 -163
  223. data/vendor/prism/include/prism/regexp.h +0 -43
  224. data/vendor/prism/include/prism/static_literals.h +0 -121
  225. data/vendor/prism/include/prism/util/pm_buffer.h +0 -236
  226. data/vendor/prism/include/prism/util/pm_char.h +0 -204
  227. data/vendor/prism/include/prism/util/pm_constant_pool.h +0 -218
  228. data/vendor/prism/include/prism/util/pm_integer.h +0 -130
  229. data/vendor/prism/include/prism/util/pm_memchr.h +0 -29
  230. data/vendor/prism/include/prism/util/pm_newline_list.h +0 -113
  231. data/vendor/prism/include/prism/util/pm_string.h +0 -200
  232. data/vendor/prism/src/pack.c +0 -509
  233. data/vendor/prism/src/util/pm_constant_pool.c +0 -342
  234. data/vendor/prism/src/util/pm_list.c +0 -49
  235. data/vendor/prism/src/util/pm_newline_list.c +0 -125
  236. data/vendor/prism/src/util/pm_string.c +0 -381
  237. data/vendor/prism/src/util/pm_strpbrk.c +0 -206
  238. data/vendor/prism/templates/include/prism/diagnostic.h.erb +0 -130
  239. data/vendor/prism/templates/rbi/prism/dsl.rbi.erb +0 -68
  240. data/vendor/prism/templates/rbi/prism/node.rbi.erb +0 -167
  241. data/vendor/prism/templates/rbi/prism/visitor.rbi.erb +0 -18
  242. data/vendor/prism/templates/sig/prism/_private/dot_visitor.rbs.erb +0 -45
  243. data/vendor/prism/templates/sig/prism/dsl.rbs.erb +0 -31
  244. data/vendor/prism/templates/sig/prism/mutation_compiler.rbs.erb +0 -7
  245. data/vendor/prism/templates/sig/prism/node.rbs.erb +0 -136
  246. data/vendor/prism/templates/sig/prism/visitor.rbs.erb +0 -17
  247. data/vendor/prism/templates/sig/prism.rbs.erb +0 -92
@@ -71,7 +71,8 @@ module Herb
71
71
  # An item is keyed by the key its template declares, through `herb-key` or `id` or a
72
72
  # `<%# herb:key %>` directive, which is what makes it survive being reordered. `Visitor`
73
73
  # decides what that key is and this evaluates it, so an item arrives under the same key the
74
- # marker around it carries. A collection that declares none falls back to position.
74
+ # marker around it carries. A collection that declares none falls back to position, counting
75
+ # from the first iteration, so a change re-renders every row from the first one that differs.
75
76
  #
76
77
  # ## A partial keeps its own values
77
78
  #
@@ -94,7 +95,7 @@ module Herb
94
95
  #
95
96
  # ## Blocks
96
97
  #
97
- # A tag that takes a block, such as `<%= form_with do |f| %>`, owns an entry holding everything
98
+ # A tag that takes a block, such as `<%= form_with do |f| %>`, owns an entry holding the markup
98
99
  # the block rendered, because the helper wraps that output in markup of its own that only the
99
100
  # helper can produce. What the template wrote inside the block keeps its own indices alongside:
100
101
  #
@@ -105,6 +106,20 @@ module Herb
105
106
  # and the whole block. Slot 1 is inside slot 0, which the client already has to reason about
106
107
  # for conditionals and collections.
107
108
  #
109
+ # A partial inside a block is the exception to that fallback. It keeps its own values on its slot,
110
+ # and the block's entry is a String that cannot hold them, so the entry leaves the partial's
111
+ # markup out:
112
+ #
113
+ # <%= form_with(model: @user) do |f| %><%= render "card" %><% end %>
114
+ # #=> { 0 => "<form></form>", 1 => { template: "app/views/_card.html.erb", ... } }
115
+ #
116
+ # That entry cannot stand in for the block, so it must not be written over a block holding a
117
+ # partial. The client never writes a block's entry while the page marks slots inside that block.
118
+ # It reports the block as deferred instead, even when nothing in the block changed.
119
+ #
120
+ # A render that comes back as markup, because its template is not compiled for slots, is written
121
+ # into the entry as usual, and the entry stays whole.
122
+ #
108
123
  # A block yielding more than once is the exception, since one index cannot stand for the several
109
124
  # values its interior took. `each` and the other iterations are compiled as collections for that
110
125
  # reason, and a helper that yields repeatedly reports the last value its interior took.
@@ -117,6 +132,7 @@ module Herb
117
132
  ITEMS_BUFFER = "__herb_items" #: String
118
133
  KEY_BUFFER = "__herb_key" #: String
119
134
  OCCURRENCE_BUFFER = "__herb_occurrence" #: String
135
+ RENDERED_BUFFER = "__herb_rendered" #: String
120
136
 
121
137
  BRANCHING_NODES = [
122
138
  "AST_ERB_IF_NODE",
@@ -297,14 +313,15 @@ module Herb
297
313
 
298
314
  return super if erb_escaped?(opening)
299
315
 
300
- should_escape = should_escape_output?(opening)
316
+ rendering = node.is_a?(Herb::AST::ERBRenderNode)
317
+ should_escape = !rendering && should_escape_output?(opening)
301
318
  index = claim(node)
302
319
 
303
320
  @tokens << [
304
321
  should_escape ? :expr_block_escaped : :expr_block,
305
322
  node.content.value.strip,
306
323
  current_context,
307
- index
324
+ [index, rendering]
308
325
  ]
309
326
 
310
327
  @last_trim_consumed_newline = false
@@ -335,7 +352,9 @@ module Herb
335
352
 
336
353
  @engine.send(:add_dynamic, value, context, index, escaped, nested: nested)
337
354
  when :expr_block, :expr_block_escaped
338
- @engine.send(:add_dynamic_block, value, extra, type == :expr_block_escaped)
355
+ index, rendering = extra
356
+
357
+ @engine.send(:add_dynamic_block, value, index, type == :expr_block_escaped, rendering: rendering)
339
358
  when :expr_block_end
340
359
  @engine.send(:add_expression_block_end, value, escaped: extra)
341
360
  end
@@ -461,7 +480,7 @@ module Herb
461
480
 
462
481
  #: (Integer) -> String?
463
482
  def item_key(index)
464
- @slot_visitor.slots[index]&.key_expression
483
+ @slot_visitor.slots[index]&.item_key
465
484
  end
466
485
 
467
486
  #: (untyped, Symbol) -> void
@@ -541,6 +560,7 @@ module Herb
541
560
  #: (String, ?Hash[Symbol, untyped]) -> void
542
561
  def initialize(input, properties = {})
543
562
  @block_depth = 0
563
+ @nested_renders = [] #: Array[[Integer?]?]
544
564
  @scopes = [] #: Array[Integer]
545
565
  @withheld = {} #: Hash[Integer, bool]
546
566
  @input = input
@@ -599,6 +619,8 @@ module Herb
599
619
  return add_block_dynamic("(#{assignment(index, value)}).fetch(-1)")
600
620
  end
601
621
 
622
+ return add_block_render(index ? assignment(index, value) : value) if nested
623
+
602
624
  return add_block_dynamic(index ? assignment(index, value) : value)
603
625
  end
604
626
  return if index.nil?
@@ -611,19 +633,24 @@ module Herb
611
633
  @slot_visitor.slots[index]&.type == :boolean_attribute
612
634
  end
613
635
 
614
- #: (String, Integer?, bool) -> void
615
- def add_dynamic_block(code, index, escaped)
636
+ #: (String, Integer?, bool, ?rendering: bool) -> void
637
+ def add_dynamic_block(code, index, escaped, rendering: false)
616
638
  @_in_expression_block = true
617
639
  @_expression_block_open_paren = true
618
640
 
619
641
  opening = escaped ? "#{@escapefunc}((" : "("
642
+ nested_render = rendering && !@block_depth.zero?
620
643
 
621
644
  @src << if @block_depth.zero?
622
645
  "; #{current_scope}[#{index}] = #{opening}#{code}"
646
+ elsif nested_render
647
+ "; #{RENDERED_BUFFER} = #{opening}#{code}"
623
648
  else
624
649
  "; #{@bufvar} << #{opening}#{code}"
625
650
  end
626
651
 
652
+ @nested_renders.push(nested_render ? [index] : nil)
653
+
627
654
  open_block
628
655
  end
629
656
 
@@ -634,6 +661,14 @@ module Herb
634
661
  close_block
635
662
 
636
663
  super
664
+
665
+ nested_render = @nested_renders.pop
666
+
667
+ return unless nested_render
668
+
669
+ index = nested_render.first
670
+
671
+ add_block_render(index ? assignment(index, RENDERED_BUFFER) : RENDERED_BUFFER)
637
672
  end
638
673
 
639
674
  #: () -> String
@@ -738,6 +773,10 @@ module Herb
738
773
  #: (Integer) -> void
739
774
  def scope_open_collection(index)
740
775
  @src << "; #{ITEMS_BUFFER}#{index} = ::Hash.new;"
776
+
777
+ slot = @slot_visitor.slots[index]
778
+
779
+ @src << "; #{slot.position_variable} = -1;" if slot&.positional?
741
780
  end
742
781
 
743
782
  #: (Integer, ?String?) -> void
@@ -752,17 +791,12 @@ module Herb
752
791
  def scope_item_end(index)
753
792
  leave_scope(index)
754
793
 
755
- return unless keyed?(index)
794
+ return unless @slot_visitor.slots[index]&.item_key
756
795
 
757
796
  @src << item_seeds(index)
758
797
  @src << "; #{ITEMS_BUFFER}#{index}[#{KEY_BUFFER}#{index}.to_s] = #{SCOPE_BUFFER}#{index};"
759
798
  end
760
799
 
761
- #: (Integer) -> bool
762
- def keyed?(index)
763
- !@slot_visitor.slots[index]&.key_expression.nil?
764
- end
765
-
766
800
  #: (Integer) -> String
767
801
  def item_seeds(index)
768
802
  names = @slot_visitor.seeded_item_states[index]
@@ -826,6 +860,11 @@ module Herb
826
860
  @slot_visitor.slots[index]&.interpolated? || false
827
861
  end
828
862
 
863
+ #: (String) -> void
864
+ def add_block_render(value)
865
+ add_block_dynamic("::Hash === (#{RENDERED_BUFFER} = (#{value})) ? \"\" : #{RENDERED_BUFFER}.to_s")
866
+ end
867
+
829
868
  #: (String) -> void
830
869
  def add_block_dynamic(value)
831
870
  @src << "; " << @bufvar << " << (" << value << ");"
@@ -186,7 +186,7 @@ module Herb
186
186
 
187
187
  #: (*untyped) { () -> void } -> void
188
188
  def indexing(*arrays)
189
- marked = arrays.select { |array| array.is_a?(Array) }
189
+ marked = arrays.grep(Array)
190
190
  marked.each { |array| @indexed[array] = true }
191
191
 
192
192
  yield
@@ -66,6 +66,7 @@ module Herb
66
66
  COVERED = "@_herb_covered" #: String
67
67
  OCCURRENCES = "@_herb_region_occurrences" #: String
68
68
  OCCURRENCE = "_herb_occurrence" #: String
69
+ POSITION = "__herb_position" #: String
69
70
  NAME_ATTRIBUTE = "data-herb-name" #: String
70
71
  KEY_ATTRIBUTE = "herb-key" #: String
71
72
 
@@ -109,6 +110,15 @@ module Herb
109
110
 
110
111
  #: () -> bool
111
112
  def interpolated? = Types.interpolated?(type)
113
+
114
+ #: () -> bool
115
+ def positional? = type == :collection && key_expression.nil?
116
+
117
+ #: () -> String
118
+ def position_variable = "#{POSITION}#{index}"
119
+
120
+ #: () -> String?
121
+ def item_key = positional? ? "#{position_variable} += 1" : key_expression
112
122
  end
113
123
 
114
124
  #: (String) -> bool
@@ -1886,6 +1896,8 @@ module Herb
1886
1896
  wrap_items(child, slot_index, namespace)
1887
1897
  mark_branches(child, slot_index, namespace)
1888
1898
 
1899
+ index += insert_position_counter(array, index, slot_index)
1900
+
1889
1901
  if slot_index && slot_index != anchored
1890
1902
  if @slots[slot_index].type == :keyed
1891
1903
  park_keyed(slot_index, child, namespace)
@@ -2072,12 +2084,23 @@ module Herb
2072
2084
  bodies
2073
2085
  end
2074
2086
 
2087
+ #: (Array[untyped], Integer, Integer?) -> Integer
2088
+ def insert_position_counter(array, index, slot_index)
2089
+ slot = slot_index ? @slots[slot_index] : nil
2090
+
2091
+ return 0 unless slot&.positional?
2092
+
2093
+ array.insert(index, erb_code_node("#{slot.position_variable} = -1"))
2094
+
2095
+ 1
2096
+ end
2097
+
2075
2098
  #: (Herb::AST::Node, Integer?, ?Symbol) -> void
2076
2099
  def wrap_items(node, slot_index, namespace = :html)
2077
2100
  return unless slot_index
2078
2101
 
2079
2102
  slot = @slots[slot_index]
2080
- return unless slot.type == :collection && slot.key_expression
2103
+ return unless slot.type == :collection
2081
2104
 
2082
2105
  BRANCH_BODY_PROPERTIES.each do |property|
2083
2106
  next unless node.respond_to?(property)
@@ -2090,13 +2113,18 @@ module Herb
2090
2113
 
2091
2114
  body.unshift(
2092
2115
  text_node(@markers.item_open_prefix(slot_index)),
2093
- erb_code_node("#{@bufvar} << ::Herb::Engine.raw((#{slot.key_expression}).to_s)"),
2094
- text_node(@markers.item_open_suffix)
2116
+ erb_code_node("#{@bufvar} << ::Herb::Engine.raw((#{slot.item_key}).to_s)"),
2117
+ text_node(@markers.item_open_suffix),
2118
+ erb_code_node("begin")
2095
2119
  )
2096
2120
 
2097
2121
  body.insert(3, erb_code_node(%(#{COVERED}[#{covered_key(key).inspect}] = true))) if parked && client?
2098
2122
 
2099
- body.push(text_node(@markers.item_close(slot_index)))
2123
+ body.push(
2124
+ erb_code_node("ensure"),
2125
+ text_node(@markers.item_close(slot_index)),
2126
+ erb_code_node("end")
2127
+ )
2100
2128
  end
2101
2129
  end
2102
2130
 
@@ -140,6 +140,8 @@ module Herb
140
140
 
141
141
  instrument(node)
142
142
 
143
+ return [node] if literal?(node)
144
+
143
145
  if framed?(node)
144
146
  [enter_node(node), node, leave_node(node)]
145
147
  elsif erb_outputs?(node)
@@ -158,6 +160,12 @@ module Herb
158
160
  replacement
159
161
  end
160
162
 
163
+ def literal?(node)
164
+ return false unless node.respond_to?(:tag_opening) && node.tag_opening
165
+
166
+ erb_escaped?(node.tag_opening.value)
167
+ end
168
+
161
169
  def framed?(node)
162
170
  return true if node.is_a?(Herb::AST::ERBRenderNode)
163
171
 
@@ -197,9 +205,9 @@ module Herb
197
205
  body = code(node)
198
206
  call = captured?(node) ? "output" : "at"
199
207
 
200
- return erb_node(node, "<%=", "#{SESSION}.#{call}(#{position(node)}) {\n#{body}\n}") if spans_lines?(body)
208
+ return rewrapped(node, "#{SESSION}.#{call}(#{position(node)}) {\n#{body}\n}") if spans_lines?(body)
201
209
 
202
- erb_node(node, "<%=", "#{SESSION}.#{call}(#{position(node)}) { #{body} }")
210
+ rewrapped(node, "#{SESSION}.#{call}(#{position(node)}) { #{body} }")
203
211
  end
204
212
 
205
213
  def captured?(node)
@@ -221,10 +229,10 @@ module Herb
221
229
  body = code(node)
222
230
 
223
231
  if spans_lines?(body)
224
- return erb_node(node, "<%", "#{SESSION}.enter(#{position(node)}); begin\n#{body}\nensure; #{SESSION}.leave; end")
232
+ return rewrapped(node, "#{SESSION}.enter(#{position(node)}); begin\n#{body}\nensure; #{SESSION}.leave; end")
225
233
  end
226
234
 
227
- erb_node(node, "<%", "#{SESSION}.enter(#{position(node)}); begin; #{body}; ensure; #{SESSION}.leave; end")
235
+ rewrapped(node, "#{SESSION}.enter(#{position(node)}); begin; #{body}; ensure; #{SESSION}.leave; end")
228
236
  end
229
237
 
230
238
  #: (String) -> bool
@@ -276,11 +284,15 @@ module Herb
276
284
  node.content&.value.to_s.strip
277
285
  end
278
286
 
279
- def erb_node(node, opening, code)
287
+ def rewrapped(node, code)
288
+ erb_node(node, node.tag_opening.value, code, closing: node.tag_closing&.value || "%>")
289
+ end
290
+
291
+ def erb_node(node, opening, code, closing: "%>")
280
292
  Herb::AST::ERBContentNode.build(
281
293
  tag_opening: Herb::Token.from("TOKEN_ERB_START", opening),
282
294
  content: Herb::Token.from("TOKEN_ERB_CONTENT", " #{code} "),
283
- tag_closing: Herb::Token.from("TOKEN_ERB_END", "%>"),
295
+ tag_closing: Herb::Token.from("TOKEN_ERB_END", closing),
284
296
  valid: true,
285
297
  location: node.location
286
298
  )
@@ -19,6 +19,7 @@ module Herb
19
19
  attr_reader :prism_nodes_deep #: bool
20
20
  attr_reader :prism_program #: bool
21
21
  attr_reader :render_nodes #: bool
22
+ attr_reader :ruby_version #: String?
22
23
  attr_reader :strict #: bool
23
24
  attr_reader :strict_locals #: bool
24
25
  attr_reader :timeout #: Numeric
@@ -45,8 +46,8 @@ module Herb
45
46
  DEFAULT_TRACK_WHITESPACE = false #: bool
46
47
  DEFAULT_TRANSFORM_CONDITIONALS = false #: bool
47
48
 
48
- #: (?action_view_helpers: bool, ?analyze: bool, ?arena_stats: bool, ?dot_notation_tags: bool, ?erb_openers: Array[String], ?herb_directives: bool, ?html: bool, ?iteration_nodes: bool, ?max_errors: Integer?, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?render_nodes: bool, ?strict: bool, ?strict_locals: bool, ?timeout: Numeric, ?track_locations: bool, ?track_whitespace: bool, ?transform_conditionals: bool) -> void
49
- def initialize(action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, analyze: DEFAULT_ANALYZE, arena_stats: DEFAULT_CAPTURE_ARENA_STATS, dot_notation_tags: DEFAULT_DOT_NOTATION_TAGS, erb_openers: [], herb_directives: DEFAULT_HERB_DIRECTIVES, html: DEFAULT_HTML, iteration_nodes: DEFAULT_ITERATION_NODES, max_errors: DEFAULT_MAX_ERRORS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, render_nodes: DEFAULT_RENDER_NODES, strict: DEFAULT_STRICT, strict_locals: DEFAULT_STRICT_LOCALS, timeout: DEFAULT_TIMEOUT, track_locations: DEFAULT_TRACK_LOCATIONS, track_whitespace: DEFAULT_TRACK_WHITESPACE, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS)
49
+ #: (?action_view_helpers: bool, ?analyze: bool, ?arena_stats: bool, ?dot_notation_tags: bool, ?erb_openers: Array[String], ?herb_directives: bool, ?html: bool, ?iteration_nodes: bool, ?max_errors: Integer?, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?render_nodes: bool, ?ruby_version: String?, ?strict: bool, ?strict_locals: bool, ?timeout: Numeric, ?track_locations: bool, ?track_whitespace: bool, ?transform_conditionals: bool) -> void
50
+ def initialize(action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, analyze: DEFAULT_ANALYZE, arena_stats: DEFAULT_CAPTURE_ARENA_STATS, dot_notation_tags: DEFAULT_DOT_NOTATION_TAGS, erb_openers: [], herb_directives: DEFAULT_HERB_DIRECTIVES, html: DEFAULT_HTML, iteration_nodes: DEFAULT_ITERATION_NODES, max_errors: DEFAULT_MAX_ERRORS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, render_nodes: DEFAULT_RENDER_NODES, ruby_version: "4.0", strict: DEFAULT_STRICT, strict_locals: DEFAULT_STRICT_LOCALS, timeout: DEFAULT_TIMEOUT, track_locations: DEFAULT_TRACK_LOCATIONS, track_whitespace: DEFAULT_TRACK_WHITESPACE, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS)
50
51
  @action_view_helpers = action_view_helpers
51
52
  @analyze = analyze
52
53
  @arena_stats = arena_stats
@@ -60,6 +61,7 @@ module Herb
60
61
  @prism_nodes_deep = prism_nodes_deep
61
62
  @prism_program = prism_program
62
63
  @render_nodes = render_nodes
64
+ @ruby_version = ruby_version
63
65
  @strict = strict
64
66
  @strict_locals = strict_locals
65
67
  @timeout = timeout
@@ -68,7 +70,7 @@ module Herb
68
70
  @transform_conditionals = transform_conditionals
69
71
  end
70
72
 
71
- #: () -> Hash[Symbol, (bool | Numeric | Array[String] | nil)]
73
+ #: () -> Hash[Symbol, (bool | Numeric | String | Array[String] | nil)]
72
74
  def to_h
73
75
  {
74
76
  action_view_helpers: @action_view_helpers,
@@ -84,6 +86,7 @@ module Herb
84
86
  prism_nodes_deep: @prism_nodes_deep,
85
87
  prism_program: @prism_program,
86
88
  render_nodes: @render_nodes,
89
+ ruby_version: @ruby_version,
87
90
  strict: @strict,
88
91
  strict_locals: @strict_locals,
89
92
  timeout: @timeout,
data/lib/herb/project.rb CHANGED
@@ -332,7 +332,7 @@ module Herb
332
332
  return unless issue_type[:key] == :invalid_ruby && file_content
333
333
 
334
334
  begin
335
- engine = Herb::Engine.new(file_content, filename: file_path, escape: true, visitors: [])
335
+ engine = Herb::Engine.new(file_content, filename: file_path, escape: true, parser_options: engine_parser_options, visitors: [])
336
336
  puts ""
337
337
  puts "**Compiled Ruby:**"
338
338
  puts "```ruby"
@@ -345,6 +345,16 @@ module Herb
345
345
 
346
346
  private
347
347
 
348
+ #: () -> Hash[Symbol, untyped]
349
+ def parser_options
350
+ @parser_options ||= configuration.parser_options
351
+ end
352
+
353
+ #: () -> Hash[Symbol, untyped]
354
+ def engine_parser_options
355
+ @engine_parser_options ||= configuration.engine_option("parser_options", {}).transform_keys(&:to_sym).merge(parser_options)
356
+ end
357
+
348
358
  def process_file(file_path)
349
359
  isolate ? process_file_isolated(file_path) : process_file_direct(file_path)
350
360
  end
@@ -362,7 +372,7 @@ module Herb
362
372
  end
363
373
 
364
374
  Timeout.timeout(file_timeout) do
365
- parse_result = Herb.parse(file_content)
375
+ parse_result = Herb.parse(file_content, **parser_options)
366
376
 
367
377
  if parse_result.failed?
368
378
  result[:file_content] = file_content
@@ -401,7 +411,7 @@ module Herb
401
411
  $stderr.reopen(stderr_file.path, "w")
402
412
 
403
413
  begin
404
- parse_result = Herb.parse(file_content)
414
+ parse_result = Herb.parse(file_content, **parser_options)
405
415
  exit!(parse_result.failed? ? 2 : 0)
406
416
  rescue StandardError => e
407
417
  warn "Ruby exception: #{e.class}: #{e.message}"
@@ -457,7 +467,7 @@ module Herb
457
467
  end
458
468
 
459
469
  def classify_parse_errors(file_path, file_content)
460
- default_result = Herb.parse(file_content)
470
+ default_result = Herb.parse(file_content, **parser_options)
461
471
 
462
472
  diagnostics = if default_result.respond_to?(:errors) && default_result.errors.any?
463
473
  default_result.errors.map do |error|
@@ -470,8 +480,8 @@ module Herb
470
480
  end
471
481
  end
472
482
 
473
- no_strict_result = Herb.parse(file_content, strict: false)
474
- no_analyze_result = Herb.parse(file_content, analyze: false)
483
+ no_strict_result = Herb.parse(file_content, **parser_options, strict: false)
484
+ no_analyze_result = Herb.parse(file_content, **parser_options, analyze: false)
475
485
 
476
486
  if no_strict_result.success?
477
487
  { status: :strict_parse_error, diagnostics: diagnostics,
@@ -496,6 +506,7 @@ module Herb
496
506
  file_content,
497
507
  filename: file_path,
498
508
  escape: true,
509
+ parser_options: engine_parser_options,
499
510
  validate_ruby: validate_ruby,
500
511
  visitors: Herb::Engine::Validators.all
501
512
  )
@@ -514,7 +525,7 @@ module Herb
514
525
 
515
526
  # Retry without validators
516
527
  begin
517
- Herb::Engine.new(file_content, filename: file_path, escape: true, visitors: [], validate_ruby: validate_ruby)
528
+ Herb::Engine.new(file_content, filename: file_path, escape: true, parser_options: engine_parser_options, visitors: [], validate_ruby: validate_ruby)
518
529
  error_name = e.is_a?(Herb::Engine::SecurityError) ? "SecurityError" : "ValidationError"
519
530
  return { status: :validation_error, file_content: file_content,
520
531
  compilation_error: compilation_error,
@@ -526,7 +537,7 @@ module Herb
526
537
 
527
538
  # Retry without strict mode
528
539
  begin
529
- Herb::Engine.new(file_content, filename: file_path, escape: true, strict: false, validate_ruby: validate_ruby)
540
+ Herb::Engine.new(file_content, filename: file_path, escape: true, parser_options: engine_parser_options, strict: false, validate_ruby: validate_ruby)
530
541
  return { status: :strict_compilation_failed, file_content: file_content,
531
542
  compilation_error: compilation_error,
532
543
  diagnostics: [{ name: "CompilationError", message: "#{e.message} (strict mode)" }],
data/lib/herb/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Herb
5
- VERSION = "0.11.1"
5
+ VERSION = "0.11.2"
6
6
  end
data/lib/herb.rb CHANGED
@@ -67,7 +67,7 @@ rescue LoadError => e
67
67
  end
68
68
 
69
69
  module Herb
70
- #: (String input, ?track_whitespace: bool, ?track_locations: bool, ?analyze: bool, ?strict: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?dot_notation_tags: bool, ?render_nodes: bool, ?strict_locals: bool, ?herb_directives: bool, ?iteration_nodes: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?html: bool, ?erb_openers: Array[String], ?arena_stats: bool) -> ParseResult
70
+ #: (String input, ?track_whitespace: bool, ?track_locations: bool, ?analyze: bool, ?strict: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?dot_notation_tags: bool, ?render_nodes: bool, ?strict_locals: bool, ?herb_directives: bool, ?iteration_nodes: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?html: bool, ?erb_openers: Array[String], ?ruby_version: String?, ?arena_stats: bool) -> ParseResult
71
71
  def self.parse(...)
72
72
  Backend.parse(...)
73
73
  end
@@ -5,6 +5,8 @@ module Herb
5
5
  module PartialResolution
6
6
  PARTIAL_PREFIX: String
7
7
 
8
+ VARIANT_SEPARATOR: String
9
+
8
10
  EXTENSIONS: Array[String]
9
11
 
10
12
  EXTENSION_ALTERNATIVES: String
@@ -41,6 +43,12 @@ module Herb
41
43
  # : (Array[String]) -> Array[String]
42
44
  def self.by_precedence: (Array[String]) -> Array[String]
43
45
 
46
+ # : (String?) -> String?
47
+ def self.template_variant: (String?) -> String?
48
+
49
+ # : (Array[String], String?) -> String?
50
+ def self.preferred_for: (Array[String], String?) -> String?
51
+
44
52
  # : (String, String | Pathname) -> String?
45
53
  def self.template_name_for: (String, String | Pathname) -> String?
46
54
 
@@ -57,6 +57,9 @@ module Herb
57
57
  # : () -> Array[String]
58
58
  def erb_openers: () -> Array[String]
59
59
 
60
+ # : () -> String?
61
+ def ruby_version: () -> String?
62
+
60
63
  # : () -> Hash[Symbol, untyped]
61
64
  def parser_options: () -> Hash[Symbol, untyped]
62
65
 
@@ -65,7 +65,8 @@ module Herb
65
65
  # An item is keyed by the key its template declares, through `herb-key` or `id` or a
66
66
  # `<%# herb:key %>` directive, which is what makes it survive being reordered. `Visitor`
67
67
  # decides what that key is and this evaluates it, so an item arrives under the same key the
68
- # marker around it carries. A collection that declares none falls back to position.
68
+ # marker around it carries. A collection that declares none falls back to position, counting
69
+ # from the first iteration, so a change re-renders every row from the first one that differs.
69
70
  #
70
71
  # ## A partial keeps its own values
71
72
  #
@@ -88,7 +89,7 @@ module Herb
88
89
  #
89
90
  # ## Blocks
90
91
  #
91
- # A tag that takes a block, such as `<%= form_with do |f| %>`, owns an entry holding everything
92
+ # A tag that takes a block, such as `<%= form_with do |f| %>`, owns an entry holding the markup
92
93
  # the block rendered, because the helper wraps that output in markup of its own that only the
93
94
  # helper can produce. What the template wrote inside the block keeps its own indices alongside:
94
95
  #
@@ -99,6 +100,20 @@ module Herb
99
100
  # and the whole block. Slot 1 is inside slot 0, which the client already has to reason about
100
101
  # for conditionals and collections.
101
102
  #
103
+ # A partial inside a block is the exception to that fallback. It keeps its own values on its slot,
104
+ # and the block's entry is a String that cannot hold them, so the entry leaves the partial's
105
+ # markup out:
106
+ #
107
+ # <%= form_with(model: @user) do |f| %><%= render "card" %><% end %>
108
+ # #=> { 0 => "<form></form>", 1 => { template: "app/views/_card.html.erb", ... } }
109
+ #
110
+ # That entry cannot stand in for the block, so it must not be written over a block holding a
111
+ # partial. The client never writes a block's entry while the page marks slots inside that block.
112
+ # It reports the block as deferred instead, even when nothing in the block changed.
113
+ #
114
+ # A render that comes back as markup, because its template is not compiled for slots, is written
115
+ # into the entry as usual, and the entry stays whole.
116
+ #
102
117
  # A block yielding more than once is the exception, since one index cannot stand for the several
103
118
  # values its interior took. `each` and the other iterations are compiled as collections for that
104
119
  # reason, and a helper that yields repeatedly reports the last value its interior took.
@@ -117,6 +132,8 @@ module Herb
117
132
 
118
133
  OCCURRENCE_BUFFER: String
119
134
 
135
+ RENDERED_BUFFER: String
136
+
120
137
  BRANCHING_NODES: Array[String]
121
138
 
122
139
  class Compiler < Herb::Engine::Compiler
@@ -249,8 +266,8 @@ module Herb
249
266
  # : (Integer) -> bool
250
267
  def boolean_presence?: (Integer) -> bool
251
268
 
252
- # : (String, Integer?, bool) -> void
253
- def add_dynamic_block: (String, Integer?, bool) -> void
269
+ # : (String, Integer?, bool, ?rendering: bool) -> void
270
+ def add_dynamic_block: (String, Integer?, bool, ?rendering: bool) -> void
254
271
 
255
272
  # : (String, ?escaped: bool) -> void
256
273
  def add_expression_block_end: (String, ?escaped: bool) -> void
@@ -305,9 +322,6 @@ module Herb
305
322
  # : (Integer) -> void
306
323
  def scope_item_end: (Integer) -> void
307
324
 
308
- # : (Integer) -> bool
309
- def keyed?: (Integer) -> bool
310
-
311
325
  # : (Integer) -> String
312
326
  def item_seeds: (Integer) -> String
313
327
 
@@ -332,6 +346,9 @@ module Herb
332
346
  # : (Integer) -> bool
333
347
  def interpolated?: (Integer) -> bool
334
348
 
349
+ # : (String) -> void
350
+ def add_block_render: (String) -> void
351
+
335
352
  # : (String) -> void
336
353
  def add_block_dynamic: (String) -> void
337
354
 
@@ -57,6 +57,8 @@ module Herb
57
57
 
58
58
  OCCURRENCE: String
59
59
 
60
+ POSITION: String
61
+
60
62
  NAME_ATTRIBUTE: String
61
63
 
62
64
  KEY_ATTRIBUTE: String
@@ -121,6 +123,15 @@ module Herb
121
123
 
122
124
  # : () -> bool
123
125
  def interpolated?: () -> bool
126
+
127
+ # : () -> bool
128
+ def positional?: () -> bool
129
+
130
+ # : () -> String
131
+ def position_variable: () -> String
132
+
133
+ # : () -> String?
134
+ def item_key: () -> String?
124
135
  end
125
136
 
126
137
  # : (String) -> bool
@@ -562,6 +573,9 @@ module Herb
562
573
  # : (untyped) -> Array[Array[Herb::AST::Node]]
563
574
  def branch_bodies: (untyped) -> Array[Array[Herb::AST::Node]]
564
575
 
576
+ # : (Array[untyped], Integer, Integer?) -> Integer
577
+ def insert_position_counter: (Array[untyped], Integer, Integer?) -> Integer
578
+
565
579
  # : (Herb::AST::Node, Integer?, ?Symbol) -> void
566
580
  def wrap_items: (Herb::AST::Node, Integer?, ?Symbol) -> void
567
581
 
@@ -72,6 +72,8 @@ module Herb
72
72
  # : (Herb::AST::Node, Herb::AST::Node) -> Herb::AST::Node
73
73
  def replacing: (Herb::AST::Node, Herb::AST::Node) -> Herb::AST::Node
74
74
 
75
+ def literal?: (untyped node) -> untyped
76
+
75
77
  def framed?: (untyped node) -> untyped
76
78
 
77
79
  def assignment?: (untyped node) -> untyped
@@ -105,7 +107,9 @@ module Herb
105
107
 
106
108
  def code: (untyped node) -> untyped
107
109
 
108
- def erb_node: (untyped node, untyped opening, untyped code) -> untyped
110
+ def rewrapped: (untyped node, untyped code) -> untyped
111
+
112
+ def erb_node: (untyped node, untyped opening, untyped code, ?closing: untyped) -> untyped
109
113
  end
110
114
  end
111
115
  end