herb 0.11.0-x86_64-linux-gnu → 0.11.2-x86_64-linux-gnu

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 (273) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -2
  3. data/config.yml +9 -0
  4. data/ext/herb/extconf.rb +8 -31
  5. data/ext/herb/nodes.c +90 -73
  6. data/ext/herb/parser_options_helpers.c +26 -0
  7. data/lib/herb/3.2/herb.so +0 -0
  8. data/lib/herb/3.3/herb.so +0 -0
  9. data/lib/herb/3.4/herb.so +0 -0
  10. data/lib/herb/4.0/herb.so +0 -0
  11. data/lib/herb/analysis/partial_index.rb +1 -1
  12. data/lib/herb/analysis/partial_resolution.rb +27 -0
  13. data/lib/herb/ast/helpers.rb +1 -1
  14. data/lib/herb/ast/nodes.rb +32 -6
  15. data/lib/herb/bootstrap.rb +6 -2
  16. data/lib/herb/configuration.rb +11 -1
  17. data/lib/herb/dev/classifier.rb +1 -1
  18. data/lib/herb/dev/runner.rb +6 -1
  19. data/lib/herb/engine/compiler.rb +52 -19
  20. data/lib/herb/engine/slots/dynamics_compiler.rb +63 -14
  21. data/lib/herb/engine/slots/markers.rb +24 -0
  22. data/lib/herb/engine/slots/state_compiler.rb +1 -2
  23. data/lib/herb/engine/slots/subtree_compiler.rb +1 -1
  24. data/lib/herb/engine/slots/visitor.rb +71 -32
  25. data/lib/herb/engine/visitors/instrumentation_visitor.rb +18 -6
  26. data/lib/herb/engine.rb +0 -2
  27. data/lib/herb/parser_options.rb +6 -3
  28. data/lib/herb/project.rb +19 -8
  29. data/lib/herb/version.rb +1 -1
  30. data/lib/herb.rb +1 -1
  31. data/sig/herb/analysis/partial_resolution.rbs +8 -0
  32. data/sig/herb/ast/nodes.rbs +10 -4
  33. data/sig/herb/bootstrap.rbs +2 -0
  34. data/sig/herb/configuration.rbs +3 -0
  35. data/sig/herb/dev/runner.rbs +3 -0
  36. data/sig/herb/engine/compiler.rbs +14 -3
  37. data/sig/herb/engine/slots/dynamics_compiler.rbs +27 -7
  38. data/sig/herb/engine/slots/markers.rbs +14 -0
  39. data/sig/herb/engine/slots/visitor.rbs +31 -13
  40. data/sig/herb/engine/visitors/instrumentation_visitor.rbs +5 -1
  41. data/sig/herb/parser_options.rbs +6 -4
  42. data/sig/herb.rbs +2 -2
  43. data/sig/serialized_ast_nodes.rbs +1 -0
  44. data/src/analyze/action_view/attribute_extraction_helpers.c +40 -21
  45. data/src/analyze/action_view/generated_handlers.c +36 -36
  46. data/src/analyze/action_view/image_tag.c +7 -3
  47. data/src/analyze/action_view/javascript_include_tag.c +6 -3
  48. data/src/analyze/action_view/link_to.c +23 -23
  49. data/src/analyze/action_view/registry.c +7 -3
  50. data/src/analyze/action_view/tag_helper_node_builders.c +12 -16
  51. data/src/analyze/action_view/tag_helpers.c +215 -110
  52. data/src/analyze/action_view/turbo_frame_tag.c +5 -4
  53. data/src/analyze/analyze.c +41 -45
  54. data/src/analyze/analyze_helpers.c +240 -405
  55. data/src/analyze/analyzed_ruby.c +10 -7
  56. data/src/analyze/builders.c +1 -0
  57. data/src/analyze/control_type.c +46 -29
  58. data/src/analyze/herb_directives.c +16 -20
  59. data/src/analyze/iteration_nodes.c +69 -24
  60. data/src/analyze/parse_errors.c +32 -22
  61. data/src/analyze/postfix_conditionals.c +23 -13
  62. data/src/analyze/prism_annotate.c +56 -32
  63. data/src/analyze/render_nodes.c +51 -41
  64. data/src/analyze/strict_locals.c +14 -15
  65. data/src/analyze/ternary_conditionals.c +14 -10
  66. data/src/ast/ast_nodes.c +3 -1
  67. data/src/ast/ast_pretty_print.c +5 -1
  68. data/src/extract.c +204 -143
  69. data/src/herb.c +10 -8
  70. data/src/include/analyze/action_view/tag_helper_handler.h +1 -1
  71. data/src/include/analyze/action_view/tag_helper_node_builders.h +1 -0
  72. data/src/include/analyze/action_view/tag_helpers.h +1 -1
  73. data/src/include/analyze/analyze.h +3 -24
  74. data/src/include/analyze/analyzed_ruby.h +29 -2
  75. data/src/include/analyze/helpers.h +7 -27
  76. data/src/include/ast/ast_nodes.h +2 -1
  77. data/src/include/herb.h +3 -2
  78. data/src/include/lexer/lexer.h +1 -0
  79. data/src/include/parser/parser_options.h +5 -0
  80. data/src/include/prism/herb_prism_node.h +6 -1
  81. data/src/include/prism/prism_context.h +12 -12
  82. data/src/include/prism/prism_helpers.h +30 -0
  83. data/src/include/prism_version.h +11 -0
  84. data/src/include/version.h +1 -1
  85. data/src/lexer.c +32 -0
  86. data/src/parser/parser_helpers.c +1 -2
  87. data/src/parser.c +6 -7
  88. data/src/parser_options_defaults.c +1 -0
  89. data/src/prism/prism_helpers.c +167 -42
  90. data/src/prism/ruby_parser.c +20 -20
  91. data/templates/ext/herb/nodes.c.erb +5 -5
  92. data/templates/ext/herb/parser_options_helpers.c.erb +33 -0
  93. data/templates/java/nodes.c.erb +6 -6
  94. data/templates/java/parser_options_helpers.c.erb +41 -2
  95. data/templates/java/parser_options_helpers.h.erb +2 -1
  96. data/templates/javascript/packages/core/src/nodes.ts.erb +21 -1
  97. data/templates/javascript/packages/node/extension/nodes.cpp.erb +5 -5
  98. data/templates/javascript/packages/node/extension/parser_options_helpers.cpp.erb +34 -2
  99. data/templates/javascript/packages/node/extension/parser_options_helpers.h.erb +1 -1
  100. data/templates/lib/herb/parser_options.rb.erb +1 -1
  101. data/templates/rust/src/ast/nodes.rs.erb +1 -4
  102. data/templates/rust/src/prism/generated_deserialize.rs.erb +90 -44
  103. data/templates/rust/src/prism/generated_prettyprint.rs.erb +18 -27
  104. data/templates/src/analyze/action_view/generated_handlers.c.erb +23 -22
  105. data/templates/src/include/parser/parser_options.h.erb +10 -0
  106. data/templates/src/include/prism_version.h.erb +17 -0
  107. data/templates/src/parser_options_defaults.c.erb +1 -1
  108. data/templates/template.rb +17 -2
  109. data/templates/wasm/nodes.cpp.erb +8 -18
  110. data/templates/wasm/parser_options_helpers.cpp.erb +24 -0
  111. data/vendor/prism/Rakefile +76 -7
  112. data/vendor/prism/config.yml +174 -161
  113. data/vendor/prism/include/prism/arena.h +45 -0
  114. data/vendor/prism/include/prism/arena.hpp +42 -0
  115. data/vendor/prism/include/prism/ast.h +2738 -611
  116. data/vendor/prism/include/prism/ast.hpp +641 -0
  117. data/vendor/prism/include/prism/buffer.h +60 -0
  118. data/vendor/prism/include/prism/buffer.hpp +55 -0
  119. data/vendor/prism/include/prism/comments.h +72 -0
  120. data/vendor/prism/include/prism/compiler/accel.h +26 -0
  121. data/vendor/prism/include/prism/compiler/align.h +36 -0
  122. data/vendor/prism/include/prism/compiler/assume.h +25 -0
  123. data/vendor/prism/include/prism/compiler/branch.h +26 -0
  124. data/vendor/prism/include/prism/compiler/bswap.h +46 -0
  125. data/vendor/prism/include/prism/compiler/cplusplus.hpp +16 -0
  126. data/vendor/prism/include/prism/compiler/endianness.h +34 -0
  127. data/vendor/prism/include/prism/compiler/exceptions.hpp +17 -0
  128. data/vendor/prism/include/prism/compiler/exported.h +24 -0
  129. data/vendor/prism/include/prism/compiler/fallthrough.h +22 -0
  130. data/vendor/prism/include/prism/compiler/filesystem.h +32 -0
  131. data/vendor/prism/include/prism/compiler/flex_array.h +19 -0
  132. data/vendor/prism/include/prism/compiler/force_inline.h +21 -0
  133. data/vendor/prism/include/prism/compiler/format.h +27 -0
  134. data/vendor/prism/include/prism/compiler/inline.h +17 -0
  135. data/vendor/prism/include/prism/compiler/lifetimebound.hpp +25 -0
  136. data/vendor/prism/include/prism/compiler/nodiscard.h +22 -0
  137. data/vendor/prism/include/prism/compiler/nonnull.h +18 -0
  138. data/vendor/prism/include/prism/compiler/unused.h +18 -0
  139. data/vendor/prism/include/prism/constant_pool.h +106 -0
  140. data/vendor/prism/include/prism/diagnostic.h +55 -402
  141. data/vendor/prism/include/prism/errors_format.h +61 -0
  142. data/vendor/prism/include/prism/excludes.h +29 -0
  143. data/vendor/prism/include/prism/integer.h +41 -0
  144. data/vendor/prism/include/prism/internal/allocator.h +68 -0
  145. data/vendor/prism/include/prism/internal/allocator_debug.h +88 -0
  146. data/vendor/prism/include/prism/internal/arena.h +108 -0
  147. data/vendor/prism/include/prism/internal/bit.h +42 -0
  148. data/vendor/prism/include/prism/internal/buffer.h +94 -0
  149. data/vendor/prism/include/prism/internal/char.h +145 -0
  150. data/vendor/prism/include/prism/internal/comments.h +54 -0
  151. data/vendor/prism/include/prism/internal/constant_pool.h +152 -0
  152. data/vendor/prism/include/prism/internal/diagnostic.h +389 -0
  153. data/vendor/prism/include/prism/{encoding.h → internal/encoding.h} +27 -68
  154. data/vendor/prism/include/prism/internal/integer.h +68 -0
  155. data/vendor/prism/include/prism/internal/isinf.h +16 -0
  156. data/vendor/prism/include/prism/internal/line_offset_list.h +34 -0
  157. data/vendor/prism/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  158. data/vendor/prism/include/prism/internal/magic_comments.h +23 -0
  159. data/vendor/prism/include/prism/internal/memchr.h +15 -0
  160. data/vendor/prism/include/prism/internal/node.h +42 -0
  161. data/vendor/prism/include/prism/internal/options.h +232 -0
  162. data/vendor/prism/include/prism/internal/parser.h +1015 -0
  163. data/vendor/prism/include/prism/internal/regexp.h +26 -0
  164. data/vendor/prism/include/prism/internal/serialize.h +39 -0
  165. data/vendor/prism/include/prism/internal/source.h +72 -0
  166. data/vendor/prism/include/prism/internal/static_literals.h +60 -0
  167. data/vendor/prism/include/prism/internal/stringy.h +30 -0
  168. data/vendor/prism/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  169. data/vendor/prism/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  170. data/vendor/prism/include/prism/internal/tokens.h +11 -0
  171. data/vendor/prism/include/prism/internal/xxh3.h +16 -0
  172. data/vendor/prism/include/prism/json.h +40 -0
  173. data/vendor/prism/include/prism/line_offset_list.h +69 -0
  174. data/vendor/prism/include/prism/magic_comments.h +53 -0
  175. data/vendor/prism/include/prism/node.h +59 -49
  176. data/vendor/prism/include/prism/node.hpp +272 -0
  177. data/vendor/prism/include/prism/node_type.hpp +38 -0
  178. data/vendor/prism/include/prism/options.h +154 -279
  179. data/vendor/prism/include/prism/options.hpp +43 -0
  180. data/vendor/prism/include/prism/parser.h +367 -870
  181. data/vendor/prism/include/prism/parser.hpp +274 -0
  182. data/vendor/prism/include/prism/prettyprint.h +14 -9
  183. data/vendor/prism/include/prism/serialize.h +123 -0
  184. data/vendor/prism/include/prism/source.h +163 -0
  185. data/vendor/prism/include/prism/stream.h +36 -0
  186. data/vendor/prism/include/prism/string_query.h +82 -0
  187. data/vendor/prism/include/prism/stringy.h +61 -0
  188. data/vendor/prism/include/prism/stringy.hpp +31 -0
  189. data/vendor/prism/include/prism/version.h +20 -3
  190. data/vendor/prism/include/prism.h +35 -320
  191. data/vendor/prism/include/prism.hpp +24 -0
  192. data/vendor/prism/src/arena.c +122 -0
  193. data/vendor/prism/src/{util/pm_buffer.c → buffer.c} +53 -21
  194. data/vendor/prism/src/{util/pm_char.c → char.c} +28 -57
  195. data/vendor/prism/src/comments.c +455 -0
  196. data/vendor/prism/src/constant_pool.c +406 -0
  197. data/vendor/prism/src/diagnostic.c +120 -68
  198. data/vendor/prism/src/encoding.c +8 -3
  199. data/vendor/prism/src/errors_format.c +519 -0
  200. data/vendor/prism/src/{util/pm_integer.c → integer.c} +38 -22
  201. data/vendor/prism/src/json.c +5733 -0
  202. data/vendor/prism/src/line_offset_list.c +100 -0
  203. data/vendor/prism/src/list.c +24 -0
  204. data/vendor/prism/src/{util/pm_memchr.c → memchr.c} +8 -6
  205. data/vendor/prism/src/node.c +3465 -7787
  206. data/vendor/prism/src/options.c +161 -54
  207. data/vendor/prism/src/parser.c +371 -0
  208. data/vendor/prism/src/prettyprint.c +421 -354
  209. data/vendor/prism/src/prism.c +8522 -7280
  210. data/vendor/prism/src/regexp.c +960 -45
  211. data/vendor/prism/src/serialize.c +2434 -2048
  212. data/vendor/prism/src/source.c +524 -0
  213. data/vendor/prism/src/static_literals.c +303 -360
  214. data/vendor/prism/src/string_query.c +180 -0
  215. data/vendor/prism/src/stringy.c +61 -0
  216. data/vendor/prism/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  217. data/vendor/prism/src/strpbrk.c +447 -0
  218. data/vendor/prism/src/{token_type.c → tokens.c} +39 -18
  219. data/vendor/prism/src/xxh3.c +292 -0
  220. data/vendor/prism/templates/ext/prism/api_node.c.erb +58 -44
  221. data/vendor/prism/templates/include/prism/ast.h.erb +67 -17
  222. data/vendor/prism/templates/include/prism/ast.hpp.erb +31 -0
  223. data/vendor/prism/templates/include/prism/internal/diagnostic.h.erb +60 -0
  224. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/AbstractNodeVisitor.java.erb +1 -1
  225. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Loader.java.erb +232 -133
  226. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Nodes.java.erb +62 -23
  227. data/vendor/prism/templates/javascript/src/deserialize.js.erb +153 -146
  228. data/vendor/prism/templates/javascript/src/nodes.js.erb +4 -9
  229. data/vendor/prism/templates/lib/prism/compiler.rb.erb +14 -5
  230. data/vendor/prism/templates/lib/prism/dispatcher.rb.erb +20 -12
  231. data/vendor/prism/templates/lib/prism/dot_visitor.rb.erb +37 -11
  232. data/vendor/prism/templates/lib/prism/dsl.rb.erb +43 -4
  233. data/vendor/prism/templates/lib/prism/inspect_visitor.rb.erb +26 -10
  234. data/vendor/prism/templates/lib/prism/mutation_compiler.rb.erb +5 -2
  235. data/vendor/prism/templates/lib/prism/node.rb.erb +283 -105
  236. data/vendor/prism/templates/lib/prism/reflection.rb.erb +11 -2
  237. data/vendor/prism/templates/lib/prism/serialize.rb.erb +291 -134
  238. data/vendor/prism/templates/lib/prism/visitor.rb.erb +18 -0
  239. data/vendor/prism/templates/src/diagnostic.c.erb +114 -63
  240. data/vendor/prism/templates/src/json.c.erb +130 -0
  241. data/vendor/prism/templates/src/node.c.erb +79 -236
  242. data/vendor/prism/templates/src/prettyprint.c.erb +24 -13
  243. data/vendor/prism/templates/src/serialize.c.erb +369 -183
  244. data/vendor/prism/templates/src/{token_type.c.erb → tokens.c.erb} +22 -11
  245. data/vendor/prism/templates/template.rb +193 -91
  246. metadata +100 -42
  247. data/vendor/prism/include/prism/defines.h +0 -260
  248. data/vendor/prism/include/prism/pack.h +0 -163
  249. data/vendor/prism/include/prism/regexp.h +0 -43
  250. data/vendor/prism/include/prism/static_literals.h +0 -121
  251. data/vendor/prism/include/prism/util/pm_buffer.h +0 -236
  252. data/vendor/prism/include/prism/util/pm_char.h +0 -204
  253. data/vendor/prism/include/prism/util/pm_constant_pool.h +0 -218
  254. data/vendor/prism/include/prism/util/pm_integer.h +0 -130
  255. data/vendor/prism/include/prism/util/pm_memchr.h +0 -29
  256. data/vendor/prism/include/prism/util/pm_newline_list.h +0 -113
  257. data/vendor/prism/include/prism/util/pm_string.h +0 -200
  258. data/vendor/prism/src/pack.c +0 -509
  259. data/vendor/prism/src/util/pm_constant_pool.c +0 -342
  260. data/vendor/prism/src/util/pm_list.c +0 -49
  261. data/vendor/prism/src/util/pm_newline_list.c +0 -125
  262. data/vendor/prism/src/util/pm_string.c +0 -381
  263. data/vendor/prism/src/util/pm_strpbrk.c +0 -206
  264. data/vendor/prism/templates/include/prism/diagnostic.h.erb +0 -130
  265. data/vendor/prism/templates/rbi/prism/dsl.rbi.erb +0 -68
  266. data/vendor/prism/templates/rbi/prism/node.rbi.erb +0 -167
  267. data/vendor/prism/templates/rbi/prism/visitor.rbi.erb +0 -18
  268. data/vendor/prism/templates/sig/prism/_private/dot_visitor.rbs.erb +0 -45
  269. data/vendor/prism/templates/sig/prism/dsl.rbs.erb +0 -31
  270. data/vendor/prism/templates/sig/prism/mutation_compiler.rbs.erb +0 -7
  271. data/vendor/prism/templates/sig/prism/node.rbs.erb +0 -136
  272. data/vendor/prism/templates/sig/prism/visitor.rbs.erb +0 -17
  273. data/vendor/prism/templates/sig/prism.rbs.erb +0 -92
@@ -4964,6 +4964,7 @@ module Herb
4964
4964
  #| tag_opening: Herb::Token?,
4965
4965
  #| content: Herb::Token?,
4966
4966
  #| tag_closing: Herb::Token?,
4967
+ #| prism_node: String?,
4967
4968
  #| }
4968
4969
  class ERBYieldNode < Node
4969
4970
  include Colors
@@ -4971,13 +4972,15 @@ module Herb
4971
4972
  attr_reader :tag_opening #: Herb::Token?
4972
4973
  attr_reader :content #: Herb::Token?
4973
4974
  attr_reader :tag_closing #: Herb::Token?
4975
+ attr_reader :prism_node #: String?
4974
4976
 
4975
- #: (String, Location, Array[Herb::Errors::Error], Herb::Token?, Herb::Token?, Herb::Token?) -> void
4976
- def initialize(type, location, errors, tag_opening, content, tag_closing)
4977
+ #: (String, Location, Array[Herb::Errors::Error], Herb::Token?, Herb::Token?, Herb::Token?, String?) -> void
4978
+ def initialize(type, location, errors, tag_opening, content, tag_closing, prism_node)
4977
4979
  super(type, location, errors)
4978
4980
  @tag_opening = tag_opening
4979
4981
  @content = content
4980
4982
  @tag_closing = tag_closing
4983
+ @prism_node = prism_node
4981
4984
  end
4982
4985
 
4983
4986
  #: () -> String
@@ -4985,9 +4988,25 @@ module Herb
4985
4988
  "AST_ERB_YIELD_NODE"
4986
4989
  end
4987
4990
 
4988
- #: (?tag_opening: Herb::Token?, ?content: Herb::Token?, ?tag_closing: Herb::Token?, ?location: Location, ?errors: Array[Herb::Errors::Error]) -> ERBYieldNode
4989
- def self.build(tag_opening: nil, content: nil, tag_closing: nil, location: Location.zero, errors: [])
4990
- new(type, location, errors, tag_opening, content, tag_closing)
4991
+ #: (?tag_opening: Herb::Token?, ?content: Herb::Token?, ?tag_closing: Herb::Token?, ?prism_node: String?, ?location: Location, ?errors: Array[Herb::Errors::Error]) -> ERBYieldNode
4992
+ def self.build(tag_opening: nil, content: nil, tag_closing: nil, prism_node: nil, location: Location.zero, errors: [])
4993
+ new(type, location, errors, tag_opening, content, tag_closing, prism_node)
4994
+ end
4995
+
4996
+ #: () -> Prism::node?
4997
+ def deserialized_prism_node
4998
+ prism_node = @prism_node
4999
+ return nil unless prism_node
5000
+ return nil unless source
5001
+
5002
+ begin
5003
+ require "prism"
5004
+ rescue LoadError
5005
+ warn "The 'prism' gem is required to deserialize Prism nodes. Add it to your Gemfile or install it with: gem install prism"
5006
+ return nil
5007
+ end
5008
+
5009
+ Prism.load(source, prism_node).value
4991
5010
  end
4992
5011
 
4993
5012
  #: () -> serialized_erb_yield_node
@@ -4996,6 +5015,7 @@ module Herb
4996
5015
  tag_opening: tag_opening,
4997
5016
  content: content,
4998
5017
  tag_closing: tag_closing,
5018
+ prism_node: prism_node,
4999
5019
  }) #: Herb::serialized_erb_yield_node
5000
5020
  end
5001
5021
 
@@ -5042,9 +5062,15 @@ module Herb
5042
5062
  output += white("├── content: ")
5043
5063
  output += content ? content.tree_inspect : magenta("∅")
5044
5064
  output += "\n"
5045
- output += white("└── tag_closing: ")
5065
+ dynamic_symbol = prism_node ? "├──" : "└──"
5066
+ output += white("#{dynamic_symbol} tag_closing: ")
5046
5067
  output += tag_closing ? tag_closing.tree_inspect : magenta("∅")
5047
5068
  output += "\n"
5069
+ if prism_node && source
5070
+ output += white("└── prism_node: ")
5071
+ output += Herb::PrismInspect.inspect_prism_serialized(prism_node, source, " ")
5072
+ output += "\n"
5073
+ end
5048
5074
  output += "\n"
5049
5075
 
5050
5076
  output.gsub(/^/, " " * indent)
@@ -70,13 +70,17 @@ module Herb
70
70
  bundler_gems_dir = File.expand_path("..", ROOT_PATH)
71
71
  candidates = Dir.glob(File.join(bundler_gems_dir, "prism-*"))
72
72
 
73
- candidates.find { |path| File.directory?(File.join(path, "src")) }
73
+ candidates.find { |path| prism_source?(path) }
74
+ end
75
+
76
+ def self.prism_source?(path)
77
+ File.directory?(File.join(path, "src")) && File.exist?(File.join(path, "config.yml"))
74
78
  end
75
79
 
76
80
  def self.find_prism_from_gem_spec
77
81
  Gem::Specification.find_all_by_name("prism")
78
82
  .map(&:full_gem_path)
79
- .find { |path| File.directory?(File.join(path, "src")) }
83
+ .find { |path| prism_source?(path) }
80
84
  end
81
85
  end
82
86
  end
@@ -100,11 +100,21 @@ module Herb
100
100
  parser["erb_openers"] || []
101
101
  end
102
102
 
103
+ #: () -> String?
104
+ def ruby_version
105
+ parser["ruby_version"]
106
+ end
107
+
103
108
  #: () -> Hash[Symbol, untyped]
104
109
  def parser_options
110
+ options = {} #: Hash[Symbol, untyped]
105
111
  openers = erb_openers
112
+ version = ruby_version
113
+
114
+ options[:erb_openers] = openers unless openers.empty?
115
+ options[:ruby_version] = version if version
106
116
 
107
- openers.empty? ? {} : { erb_openers: openers }
117
+ options
108
118
  end
109
119
 
110
120
  def linter
@@ -61,7 +61,7 @@ module Herb
61
61
 
62
62
  return classification(:whitespace, operations: operations) if significant.empty?
63
63
 
64
- kind = self.class.can_patch?(significant) && @parser_options.empty? ? :static : :dynamic
64
+ kind = self.class.can_patch?(significant) && @parser_options.except(:ruby_version).empty? ? :static : :dynamic
65
65
 
66
66
  classification(kind, operations: operations, node_path: covering_path(significant))
67
67
  end
@@ -146,7 +146,12 @@ module Herb
146
146
 
147
147
  #: () -> bool
148
148
  def interactive?
149
- $stdout.tty?
149
+ $stdout.tty? && !process_manager?
150
+ end
151
+
152
+ #: () -> bool
153
+ def process_manager?
154
+ ENV.key?("PS")
150
155
  end
151
156
 
152
157
  #: (String) -> void
@@ -238,6 +238,10 @@ module Herb
238
238
  process_erb_tag(node)
239
239
  end
240
240
 
241
+ def visit_erb_strict_locals_node(node)
242
+ process_erb_tag(node)
243
+ end
244
+
241
245
  def visit_erb_control_node(node, &)
242
246
  if node.content
243
247
  if node.tag_opening && erb_escaped?(node.tag_opening.value)
@@ -282,7 +286,7 @@ module Herb
282
286
  end
283
287
 
284
288
  def visit_erb_case_node(node)
285
- visit_erb_control_with_parts(node, *case_parts(node))
289
+ visit_erb_case_parts(node)
286
290
  end
287
291
 
288
292
  def visit_erb_when_node(node)
@@ -318,7 +322,7 @@ module Herb
318
322
  end
319
323
 
320
324
  def visit_erb_case_match_node(node)
321
- visit_erb_control_with_parts(node, *case_parts(node))
325
+ visit_erb_case_parts(node)
322
326
  end
323
327
 
324
328
  def visit_erb_in_node(node)
@@ -389,14 +393,18 @@ module Herb
389
393
  end
390
394
  end
391
395
 
392
- def case_parts(node)
396
+ def visit_erb_case_parts(node)
393
397
  parts = [:conditions, :else_clause, :end_node]
394
398
 
395
- erb_escaped?(node.tag_opening.value) ? [:children, *parts] : parts
399
+ return visit_erb_control_with_parts(node, :children, *parts) if erb_escaped?(node.tag_opening.value)
400
+
401
+ visit_erb_control_with_parts(node, *parts) { skip_case_children(node.children) }
396
402
  end
397
403
 
398
- def visit_erb_control_with_parts(node, *parts)
404
+ def visit_erb_control_with_parts(node, *parts, &)
399
405
  visit_erb_control_node(node) do
406
+ yield if block_given?
407
+
400
408
  parts.each do |part|
401
409
  value = node.send(part)
402
410
  case value
@@ -813,15 +821,17 @@ module Herb
813
821
 
814
822
  return true if trailing_indentation?(text)
815
823
 
816
- whitespace_only?(text) && (preceding_text_ends_with_newline? || @last_trim_consumed_newline)
824
+ whitespace_only?(text) && (preceding_token_value_ends_with_newline? || @last_trim_consumed_newline)
817
825
  end
818
826
 
819
- def preceding_text_ends_with_newline?
827
+ def preceding_token_value_ends_with_newline?
820
828
  return true unless @tokens.length >= 2
821
829
 
822
830
  preceding = @tokens[-2]
823
831
 
824
- preceding[0] == :text && preceding[1].end_with?("\n")
832
+ return @last_trim_consumed_newline if EXPRESSION_TOKEN_TYPES.include?(preceding[0])
833
+
834
+ preceding[1].end_with?("\n")
825
835
  end
826
836
 
827
837
  def extract_and_remove_leading_space!
@@ -846,8 +856,11 @@ module Herb
846
856
 
847
857
  follows_newline = leading_space_follows_newline?
848
858
  removed_whitespace = left_trim?(node) ? remove_trailing_whitespace_from_last_token! : ""
859
+ line_start = at_line_start?
849
860
 
850
- if at_line_start?
861
+ drop_pending_trim_newline! if follows_trimmed_statement_tag?(node)
862
+
863
+ if line_start
851
864
  leading_space = extract_and_remove_leading_space!
852
865
  effective_leading_space = leading_space.empty? ? removed_whitespace : leading_space
853
866
  right_space = if Herb::Engine::Helpers.heredoc?(code)
@@ -858,15 +871,12 @@ module Herb
858
871
  " \n"
859
872
  end
860
873
 
861
- @pending_leading_whitespace_insert_index = @tokens.length
862
- @pending_leading_whitespace = effective_leading_space if !effective_leading_space.empty? && follows_newline
874
+ save_pending_leading_whitespace!(effective_leading_space) if !effective_leading_space.empty? && follows_newline
863
875
  @tokens << [:code, "#{effective_leading_space}#{code}#{right_space}", current_context]
864
876
  @trim_next_whitespace = true
865
877
 
866
- if right_space.end_with?(" \n")
867
- @pending_trim_newline_index = @tokens.length - 1
868
- @pending_trim_owns_next_whitespace = true
869
- end
878
+ @pending_trim_newline_index = @tokens.length - 1 if right_space.end_with?(" \n")
879
+ @pending_trim_owns_next_whitespace = true if right_space.end_with?(" \n") || without_source?(node)
870
880
  else
871
881
  @tokens << [:code, code, current_context]
872
882
  end
@@ -899,20 +909,43 @@ module Herb
899
909
  #: (bool) -> void
900
910
  def settle_pending_trim_newline!(keep)
901
911
  index = @pending_trim_newline_index
902
-
903
- return unless index
904
-
905
912
  owned = @pending_trim_owns_next_whitespace
906
913
 
914
+ return unless index || owned
915
+
907
916
  @pending_trim_newline_index = nil
908
917
  @pending_trim_owns_next_whitespace = false
909
918
 
910
919
  return if keep
911
920
 
912
- @tokens[index][1] = @tokens[index][1].chomp
921
+ @tokens[index][1] = @tokens[index][1].chomp if index
913
922
  @trim_next_whitespace = false if owned
914
923
  end
915
924
 
925
+ #: (Array[untyped]) -> void
926
+ def skip_case_children(children)
927
+ text = children.grep(Herb::AST::HTMLTextNode).map(&:content).join
928
+
929
+ settle_pending_trim_newline!(true) if text.match?(/\A[ \t]*\r?\n/)
930
+ end
931
+
932
+ #: (untyped) -> bool
933
+ def follows_trimmed_statement_tag?(node)
934
+ return false if without_source?(node)
935
+
936
+ !@pending_trim_newline_index.nil? && @pending_trim_newline_index == @tokens.length - 1
937
+ end
938
+
939
+ #: () -> void
940
+ def drop_pending_trim_newline!
941
+ index = @pending_trim_newline_index
942
+ return unless index
943
+
944
+ @tokens[index][1] = @tokens[index][1].chomp
945
+ @pending_trim_newline_index = nil
946
+ @pending_trim_owns_next_whitespace = false
947
+ end
948
+
916
949
  def save_pending_leading_whitespace!(whitespace)
917
950
  @pending_leading_whitespace = whitespace
918
951
  @pending_leading_whitespace_insert_index = @tokens.length
@@ -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",
@@ -189,6 +205,16 @@ module Herb
189
205
  @current_node = previous
190
206
  end
191
207
 
208
+ #: (untyped) -> void
209
+ def visit_ruby_literal_node(node)
210
+ previous = @current_node
211
+ @current_node = node
212
+
213
+ super
214
+ ensure
215
+ @current_node = previous
216
+ end
217
+
192
218
  #: (untyped) -> void
193
219
  def visit_erb_yield_node(node)
194
220
  previous = @current_node
@@ -287,14 +313,15 @@ module Herb
287
313
 
288
314
  return super if erb_escaped?(opening)
289
315
 
290
- should_escape = should_escape_output?(opening)
316
+ rendering = node.is_a?(Herb::AST::ERBRenderNode)
317
+ should_escape = !rendering && should_escape_output?(opening)
291
318
  index = claim(node)
292
319
 
293
320
  @tokens << [
294
321
  should_escape ? :expr_block_escaped : :expr_block,
295
322
  node.content.value.strip,
296
323
  current_context,
297
- index
324
+ [index, rendering]
298
325
  ]
299
326
 
300
327
  @last_trim_consumed_newline = false
@@ -325,7 +352,9 @@ module Herb
325
352
 
326
353
  @engine.send(:add_dynamic, value, context, index, escaped, nested: nested)
327
354
  when :expr_block, :expr_block_escaped
328
- @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)
329
358
  when :expr_block_end
330
359
  @engine.send(:add_expression_block_end, value, escaped: extra)
331
360
  end
@@ -451,7 +480,7 @@ module Herb
451
480
 
452
481
  #: (Integer) -> String?
453
482
  def item_key(index)
454
- @slot_visitor.slots[index]&.key_expression
483
+ @slot_visitor.slots[index]&.item_key
455
484
  end
456
485
 
457
486
  #: (untyped, Symbol) -> void
@@ -531,6 +560,7 @@ module Herb
531
560
  #: (String, ?Hash[Symbol, untyped]) -> void
532
561
  def initialize(input, properties = {})
533
562
  @block_depth = 0
563
+ @nested_renders = [] #: Array[[Integer?]?]
534
564
  @scopes = [] #: Array[Integer]
535
565
  @withheld = {} #: Hash[Integer, bool]
536
566
  @input = input
@@ -589,6 +619,8 @@ module Herb
589
619
  return add_block_dynamic("(#{assignment(index, value)}).fetch(-1)")
590
620
  end
591
621
 
622
+ return add_block_render(index ? assignment(index, value) : value) if nested
623
+
592
624
  return add_block_dynamic(index ? assignment(index, value) : value)
593
625
  end
594
626
  return if index.nil?
@@ -601,19 +633,24 @@ module Herb
601
633
  @slot_visitor.slots[index]&.type == :boolean_attribute
602
634
  end
603
635
 
604
- #: (String, Integer?, bool) -> void
605
- def add_dynamic_block(code, index, escaped)
636
+ #: (String, Integer?, bool, ?rendering: bool) -> void
637
+ def add_dynamic_block(code, index, escaped, rendering: false)
606
638
  @_in_expression_block = true
607
639
  @_expression_block_open_paren = true
608
640
 
609
641
  opening = escaped ? "#{@escapefunc}((" : "("
642
+ nested_render = rendering && !@block_depth.zero?
610
643
 
611
644
  @src << if @block_depth.zero?
612
645
  "; #{current_scope}[#{index}] = #{opening}#{code}"
646
+ elsif nested_render
647
+ "; #{RENDERED_BUFFER} = #{opening}#{code}"
613
648
  else
614
649
  "; #{@bufvar} << #{opening}#{code}"
615
650
  end
616
651
 
652
+ @nested_renders.push(nested_render ? [index] : nil)
653
+
617
654
  open_block
618
655
  end
619
656
 
@@ -624,6 +661,14 @@ module Herb
624
661
  close_block
625
662
 
626
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)
627
672
  end
628
673
 
629
674
  #: () -> String
@@ -728,6 +773,10 @@ module Herb
728
773
  #: (Integer) -> void
729
774
  def scope_open_collection(index)
730
775
  @src << "; #{ITEMS_BUFFER}#{index} = ::Hash.new;"
776
+
777
+ slot = @slot_visitor.slots[index]
778
+
779
+ @src << "; #{slot.position_variable} = -1;" if slot&.positional?
731
780
  end
732
781
 
733
782
  #: (Integer, ?String?) -> void
@@ -742,17 +791,12 @@ module Herb
742
791
  def scope_item_end(index)
743
792
  leave_scope(index)
744
793
 
745
- return unless keyed?(index)
794
+ return unless @slot_visitor.slots[index]&.item_key
746
795
 
747
796
  @src << item_seeds(index)
748
797
  @src << "; #{ITEMS_BUFFER}#{index}[#{KEY_BUFFER}#{index}.to_s] = #{SCOPE_BUFFER}#{index};"
749
798
  end
750
799
 
751
- #: (Integer) -> bool
752
- def keyed?(index)
753
- !@slot_visitor.slots[index]&.key_expression.nil?
754
- end
755
-
756
800
  #: (Integer) -> String
757
801
  def item_seeds(index)
758
802
  names = @slot_visitor.seeded_item_states[index]
@@ -816,6 +860,11 @@ module Herb
816
860
  @slot_visitor.slots[index]&.interpolated? || false
817
861
  end
818
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
+
819
868
  #: (String) -> void
820
869
  def add_block_dynamic(value)
821
870
  @src << "; " << @bufvar << " << (" << value << ");"
@@ -10,6 +10,10 @@ module Herb
10
10
  SEED_VALUE_TYPES = "[true, false, ::Integer, ::String, ::Symbol, nil]" #: String
11
11
  PREFIX = "herb-" #: String
12
12
  COMMENT = %r{\A<!--/?#{PREFIX}} #: Regexp
13
+ FOREIGN_ATTRIBUTE = "data-herb-foreign" #: String
14
+ FOREIGN_ROOTS = { svg: "svg", math: "math" }.freeze #: Hash[Symbol, String]
15
+ SVG_INTEGRATION_POINTS = ["foreignobject", "desc", "title"].freeze #: Array[String]
16
+ MATH_INTEGRATION_POINTS = ["mi", "mo", "mn", "ms", "mtext"].freeze #: Array[String]
13
17
 
14
18
  #: (String) -> bool
15
19
  def self.marker?(text)
@@ -21,6 +25,18 @@ module Herb
21
25
  "{ #{pairs} }.select { |_, v| #{SEED_VALUE_TYPES}.any? { |t| t === v } }.transform_values { |v| v.is_a?(::Symbol) ? v.to_s : v }"
22
26
  end
23
27
 
28
+ #: (Symbol, String) -> Symbol
29
+ def self.namespace_within(namespace, tag_name)
30
+ case namespace
31
+ when :svg
32
+ SVG_INTEGRATION_POINTS.include?(tag_name) ? :html : :svg
33
+ when :math
34
+ MATH_INTEGRATION_POINTS.include?(tag_name) ? :html : :math
35
+ else
36
+ FOREIGN_ROOTS.key(tag_name) || :html
37
+ end
38
+ end
39
+
24
40
  #: (Integer, Symbol) -> String
25
41
  def slot_open(index, type)
26
42
  return "<!--#{PREFIX}slot:#{index}-->" if type == DEFAULT_TYPE
@@ -28,6 +44,14 @@ module Herb
28
44
  "<!--#{PREFIX}slot:#{index}:#{type}-->"
29
45
  end
30
46
 
47
+ #: (String, Symbol) -> String
48
+ def foreign(markup, namespace)
49
+ root = FOREIGN_ROOTS[namespace]
50
+ return markup unless root
51
+
52
+ %(<#{root} #{FOREIGN_ATTRIBUTE}>#{markup}</#{root}>)
53
+ end
54
+
31
55
  #: (Array[[Integer, Symbol, String?]]) -> String
32
56
  def element_anchors(anchors)
33
57
  anchors.map { |index, type, name| name ? "#{index}:#{type}:#{name}" : "#{index}:#{type}" }.join(" ")
@@ -1311,8 +1311,7 @@ module Herb
1311
1311
  return nil unless position
1312
1312
 
1313
1313
  condition = StateDirectives.condition_source(read)
1314
- spacing = open_tag.is_a?(Herb::AST::ERBOpenTagNode) ? " " : ""
1315
- replacement = @visitor.erb_output_node(%[("#{spacing}#{slot.attribute}" if #{condition})])
1314
+ replacement = @visitor.erb_output_node(%[("#{@visitor.attribute_spacing(children, position)}#{slot.attribute}" if #{condition})])
1316
1315
 
1317
1316
  children[position] = replacement
1318
1317
 
@@ -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