herb 0.8.10-arm-linux-gnu → 0.9.1-arm-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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +11 -3
  3. data/README.md +64 -34
  4. data/Rakefile +48 -40
  5. data/config.yml +473 -34
  6. data/ext/herb/error_helpers.c +535 -140
  7. data/ext/herb/error_helpers.h +1 -0
  8. data/ext/herb/extconf.rb +67 -28
  9. data/ext/herb/extension.c +321 -51
  10. data/ext/herb/extension.h +1 -0
  11. data/ext/herb/extension_helpers.c +24 -14
  12. data/ext/herb/extension_helpers.h +2 -2
  13. data/ext/herb/nodes.c +647 -270
  14. data/ext/herb/nodes.h +1 -0
  15. data/herb.gemspec +3 -2
  16. data/lib/herb/3.0/herb.so +0 -0
  17. data/lib/herb/3.1/herb.so +0 -0
  18. data/lib/herb/3.2/herb.so +0 -0
  19. data/lib/herb/3.3/herb.so +0 -0
  20. data/lib/herb/3.4/herb.so +0 -0
  21. data/lib/herb/4.0/herb.so +0 -0
  22. data/lib/herb/ast/helpers.rb +3 -3
  23. data/lib/herb/ast/node.rb +15 -2
  24. data/lib/herb/ast/nodes.rb +1530 -179
  25. data/lib/herb/bootstrap.rb +87 -0
  26. data/lib/herb/cli.rb +341 -31
  27. data/lib/herb/configuration.rb +248 -0
  28. data/lib/herb/defaults.yml +32 -0
  29. data/lib/herb/engine/compiler.rb +78 -11
  30. data/lib/herb/engine/debug_visitor.rb +13 -3
  31. data/lib/herb/engine/error_formatter.rb +13 -9
  32. data/lib/herb/engine/parser_error_overlay.rb +10 -6
  33. data/lib/herb/engine/validator.rb +8 -3
  34. data/lib/herb/engine/validators/nesting_validator.rb +2 -2
  35. data/lib/herb/engine.rb +119 -43
  36. data/lib/herb/errors.rb +808 -88
  37. data/lib/herb/lex_result.rb +1 -0
  38. data/lib/herb/location.rb +7 -3
  39. data/lib/herb/parse_result.rb +12 -2
  40. data/lib/herb/parser_options.rb +62 -0
  41. data/lib/herb/position.rb +1 -0
  42. data/lib/herb/prism_inspect.rb +120 -0
  43. data/lib/herb/project.rb +923 -331
  44. data/lib/herb/range.rb +1 -0
  45. data/lib/herb/token.rb +7 -1
  46. data/lib/herb/version.rb +1 -1
  47. data/lib/herb/visitor.rb +47 -2
  48. data/lib/herb/warnings.rb +6 -1
  49. data/lib/herb.rb +35 -3
  50. data/sig/herb/ast/helpers.rbs +2 -2
  51. data/sig/herb/ast/node.rbs +12 -2
  52. data/sig/herb/ast/nodes.rbs +773 -128
  53. data/sig/herb/bootstrap.rbs +31 -0
  54. data/sig/herb/configuration.rbs +89 -0
  55. data/sig/herb/engine/compiler.rbs +9 -1
  56. data/sig/herb/engine/debug_visitor.rbs +2 -0
  57. data/sig/herb/engine/validator.rbs +5 -1
  58. data/sig/herb/engine.rbs +21 -3
  59. data/sig/herb/errors.rbs +372 -63
  60. data/sig/herb/location.rbs +4 -0
  61. data/sig/herb/parse_result.rbs +4 -2
  62. data/sig/herb/parser_options.rbs +46 -0
  63. data/sig/herb/position.rbs +1 -0
  64. data/sig/herb/prism_inspect.rbs +28 -0
  65. data/sig/herb/range.rbs +1 -0
  66. data/sig/herb/token.rbs +6 -0
  67. data/sig/herb/visitor.rbs +31 -4
  68. data/sig/herb/warnings.rbs +6 -1
  69. data/sig/herb.rbs +14 -0
  70. data/sig/herb_c_extension.rbs +5 -2
  71. data/sig/rubyvm.rbs +5 -0
  72. data/sig/serialized_ast_errors.rbs +82 -6
  73. data/sig/serialized_ast_nodes.rbs +91 -6
  74. data/src/analyze/action_view/attribute_extraction_helpers.c +303 -0
  75. data/src/analyze/action_view/content_tag.c +78 -0
  76. data/src/analyze/action_view/link_to.c +167 -0
  77. data/src/analyze/action_view/registry.c +83 -0
  78. data/src/analyze/action_view/tag.c +70 -0
  79. data/src/analyze/action_view/tag_helper_node_builders.c +305 -0
  80. data/src/analyze/action_view/tag_helpers.c +815 -0
  81. data/src/analyze/action_view/turbo_frame_tag.c +88 -0
  82. data/src/analyze/analyze.c +885 -0
  83. data/src/{analyzed_ruby.c → analyze/analyzed_ruby.c} +13 -11
  84. data/src/analyze/builders.c +343 -0
  85. data/src/analyze/conditional_elements.c +594 -0
  86. data/src/analyze/conditional_open_tags.c +640 -0
  87. data/src/analyze/control_type.c +250 -0
  88. data/src/{analyze_helpers.c → analyze/helpers.c} +48 -23
  89. data/src/analyze/invalid_structures.c +193 -0
  90. data/src/{analyze_missing_end.c → analyze/missing_end.c} +33 -22
  91. data/src/analyze/parse_errors.c +84 -0
  92. data/src/analyze/prism_annotate.c +399 -0
  93. data/src/analyze/render_nodes.c +761 -0
  94. data/src/{analyze_transform.c → analyze/transform.c} +24 -3
  95. data/src/ast_node.c +17 -7
  96. data/src/ast_nodes.c +759 -387
  97. data/src/ast_pretty_print.c +264 -6
  98. data/src/errors.c +1454 -519
  99. data/src/extract.c +145 -49
  100. data/src/herb.c +52 -34
  101. data/src/html_util.c +241 -12
  102. data/src/include/analyze/action_view/attribute_extraction_helpers.h +36 -0
  103. data/src/include/analyze/action_view/tag_helper_handler.h +43 -0
  104. data/src/include/analyze/action_view/tag_helper_node_builders.h +70 -0
  105. data/src/include/analyze/action_view/tag_helpers.h +38 -0
  106. data/src/include/{analyze.h → analyze/analyze.h} +14 -4
  107. data/src/include/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
  108. data/src/include/analyze/builders.h +27 -0
  109. data/src/include/analyze/conditional_elements.h +9 -0
  110. data/src/include/analyze/conditional_open_tags.h +9 -0
  111. data/src/include/analyze/control_type.h +14 -0
  112. data/src/include/{analyze_helpers.h → analyze/helpers.h} +4 -2
  113. data/src/include/analyze/invalid_structures.h +11 -0
  114. data/src/include/analyze/prism_annotate.h +16 -0
  115. data/src/include/analyze/render_nodes.h +11 -0
  116. data/src/include/ast_node.h +11 -5
  117. data/src/include/ast_nodes.h +154 -38
  118. data/src/include/ast_pretty_print.h +5 -0
  119. data/src/include/element_source.h +3 -8
  120. data/src/include/errors.h +206 -55
  121. data/src/include/extract.h +21 -5
  122. data/src/include/herb.h +18 -6
  123. data/src/include/herb_prism_node.h +13 -0
  124. data/src/include/html_util.h +7 -2
  125. data/src/include/io.h +3 -1
  126. data/src/include/lex_helpers.h +29 -0
  127. data/src/include/lexer.h +1 -1
  128. data/src/include/lexer_peek_helpers.h +87 -13
  129. data/src/include/lexer_struct.h +2 -0
  130. data/src/include/location.h +2 -1
  131. data/src/include/parser.h +28 -2
  132. data/src/include/parser_helpers.h +19 -3
  133. data/src/include/pretty_print.h +10 -5
  134. data/src/include/prism_context.h +45 -0
  135. data/src/include/prism_helpers.h +10 -7
  136. data/src/include/prism_serialized.h +12 -0
  137. data/src/include/token.h +16 -4
  138. data/src/include/token_struct.h +10 -3
  139. data/src/include/utf8.h +2 -1
  140. data/src/include/util/hb_allocator.h +78 -0
  141. data/src/include/util/hb_arena.h +6 -1
  142. data/src/include/util/hb_arena_debug.h +12 -1
  143. data/src/include/util/hb_array.h +7 -3
  144. data/src/include/util/hb_buffer.h +6 -4
  145. data/src/include/util/hb_foreach.h +79 -0
  146. data/src/include/util/hb_narray.h +8 -4
  147. data/src/include/util/hb_string.h +56 -9
  148. data/src/include/util.h +6 -3
  149. data/src/include/version.h +1 -1
  150. data/src/io.c +3 -2
  151. data/src/lexer.c +42 -30
  152. data/src/lexer_peek_helpers.c +12 -74
  153. data/src/location.c +2 -2
  154. data/src/main.c +53 -28
  155. data/src/parser.c +784 -247
  156. data/src/parser_helpers.c +110 -23
  157. data/src/parser_match_tags.c +129 -48
  158. data/src/pretty_print.c +29 -24
  159. data/src/prism_helpers.c +30 -27
  160. data/src/ruby_parser.c +2 -0
  161. data/src/token.c +151 -66
  162. data/src/token_matchers.c +0 -1
  163. data/src/utf8.c +7 -6
  164. data/src/util/hb_allocator.c +341 -0
  165. data/src/util/hb_arena.c +81 -56
  166. data/src/util/hb_arena_debug.c +32 -17
  167. data/src/util/hb_array.c +30 -15
  168. data/src/util/hb_buffer.c +17 -21
  169. data/src/util/hb_narray.c +22 -7
  170. data/src/util/hb_string.c +49 -35
  171. data/src/util.c +21 -11
  172. data/src/visitor.c +67 -0
  173. data/templates/ext/herb/error_helpers.c.erb +24 -11
  174. data/templates/ext/herb/error_helpers.h.erb +1 -0
  175. data/templates/ext/herb/nodes.c.erb +50 -16
  176. data/templates/ext/herb/nodes.h.erb +1 -0
  177. data/templates/java/error_helpers.c.erb +1 -1
  178. data/templates/java/nodes.c.erb +30 -8
  179. data/templates/java/org/herb/ast/Errors.java.erb +24 -1
  180. data/templates/java/org/herb/ast/Nodes.java.erb +80 -21
  181. data/templates/javascript/packages/core/src/errors.ts.erb +16 -3
  182. data/templates/javascript/packages/core/src/node-type-guards.ts.erb +3 -1
  183. data/templates/javascript/packages/core/src/nodes.ts.erb +109 -32
  184. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +13 -4
  185. data/templates/javascript/packages/node/extension/nodes.cpp.erb +43 -4
  186. data/templates/lib/herb/ast/nodes.rb.erb +95 -32
  187. data/templates/lib/herb/errors.rb.erb +15 -3
  188. data/templates/lib/herb/visitor.rb.erb +2 -2
  189. data/templates/rust/src/ast/nodes.rs.erb +97 -44
  190. data/templates/rust/src/errors.rs.erb +2 -1
  191. data/templates/rust/src/nodes.rs.erb +168 -16
  192. data/templates/rust/src/union_types.rs.erb +60 -0
  193. data/templates/rust/src/visitor.rs.erb +81 -0
  194. data/templates/src/{analyze_missing_end.c.erb → analyze/missing_end.c.erb} +9 -6
  195. data/templates/src/{analyze_transform.c.erb → analyze/transform.c.erb} +2 -2
  196. data/templates/src/ast_nodes.c.erb +34 -26
  197. data/templates/src/ast_pretty_print.c.erb +24 -5
  198. data/templates/src/errors.c.erb +60 -54
  199. data/templates/src/include/ast_nodes.h.erb +6 -2
  200. data/templates/src/include/ast_pretty_print.h.erb +5 -0
  201. data/templates/src/include/errors.h.erb +15 -11
  202. data/templates/src/include/util/hb_foreach.h.erb +20 -0
  203. data/templates/src/parser_match_tags.c.erb +10 -4
  204. data/templates/src/visitor.c.erb +2 -2
  205. data/templates/template.rb +204 -29
  206. data/templates/wasm/error_helpers.cpp.erb +9 -5
  207. data/templates/wasm/nodes.cpp.erb +41 -4
  208. metadata +60 -16
  209. data/src/analyze.c +0 -1608
  210. data/src/element_source.c +0 -12
  211. data/src/include/util/hb_system.h +0 -9
  212. data/src/util/hb_system.c +0 -30
@@ -0,0 +1,885 @@
1
+ #include "../include/analyze/analyze.h"
2
+ #include "../include/analyze/action_view/tag_helper_handler.h"
3
+ #include "../include/analyze/action_view/tag_helpers.h"
4
+ #include "../include/analyze/analyzed_ruby.h"
5
+ #include "../include/analyze/builders.h"
6
+ #include "../include/analyze/conditional_elements.h"
7
+ #include "../include/analyze/conditional_open_tags.h"
8
+ #include "../include/analyze/control_type.h"
9
+ #include "../include/analyze/helpers.h"
10
+ #include "../include/analyze/invalid_structures.h"
11
+ #include "../include/analyze/render_nodes.h"
12
+ #include "../include/ast_node.h"
13
+ #include "../include/ast_nodes.h"
14
+ #include "../include/errors.h"
15
+ #include "../include/location.h"
16
+ #include "../include/parser.h"
17
+ #include "../include/position.h"
18
+ #include "../include/token_struct.h"
19
+ #include "../include/util/hb_array.h"
20
+ #include "../include/util/hb_string.h"
21
+ #include "../include/util/string.h"
22
+ #include "../include/visitor.h"
23
+
24
+ #include <prism.h>
25
+ #include <stdbool.h>
26
+ #include <stdio.h>
27
+ #include <stdlib.h>
28
+ #include <string.h>
29
+
30
+ static analyzed_ruby_T* herb_analyze_ruby(hb_string_T source) {
31
+ analyzed_ruby_T* analyzed = init_analyzed_ruby(source);
32
+
33
+ pm_visit_node(analyzed->root, search_if_nodes, analyzed);
34
+ pm_visit_node(analyzed->root, search_block_nodes, analyzed);
35
+ pm_visit_node(analyzed->root, search_case_nodes, analyzed);
36
+ pm_visit_node(analyzed->root, search_case_match_nodes, analyzed);
37
+ pm_visit_node(analyzed->root, search_while_nodes, analyzed);
38
+ pm_visit_node(analyzed->root, search_for_nodes, analyzed);
39
+ pm_visit_node(analyzed->root, search_until_nodes, analyzed);
40
+ pm_visit_node(analyzed->root, search_begin_nodes, analyzed);
41
+ pm_visit_node(analyzed->root, search_unless_nodes, analyzed);
42
+ pm_visit_node(analyzed->root, search_when_nodes, analyzed);
43
+ pm_visit_node(analyzed->root, search_in_nodes, analyzed);
44
+
45
+ search_unexpected_elsif_nodes(analyzed);
46
+ search_unexpected_else_nodes(analyzed);
47
+ search_unexpected_end_nodes(analyzed);
48
+ search_unexpected_when_nodes(analyzed);
49
+ search_unexpected_in_nodes(analyzed);
50
+
51
+ search_unexpected_rescue_nodes(analyzed);
52
+ search_unexpected_ensure_nodes(analyzed);
53
+ search_yield_nodes(analyzed->root, analyzed);
54
+ search_then_keywords(analyzed->root, analyzed);
55
+ search_unexpected_block_closing_nodes(analyzed);
56
+
57
+ if (!analyzed->valid) { pm_visit_node(analyzed->root, search_unclosed_control_flows, analyzed); }
58
+
59
+ return analyzed;
60
+ }
61
+
62
+ typedef struct {
63
+ const parser_options_T* options;
64
+ hb_allocator_T* allocator;
65
+ } analyze_erb_content_context_T;
66
+
67
+ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
68
+ analyze_erb_content_context_T* ctx = (analyze_erb_content_context_T*) data;
69
+ const parser_options_T* options = ctx->options;
70
+ hb_allocator_T* allocator = ctx->allocator;
71
+
72
+ if (node->type == AST_ERB_CONTENT_NODE) {
73
+ AST_ERB_CONTENT_NODE_T* erb_content_node = (AST_ERB_CONTENT_NODE_T*) node;
74
+
75
+ hb_string_T opening = erb_content_node->tag_opening->value;
76
+
77
+ if (!hb_string_equals(opening, hb_string("<%%")) && !hb_string_equals(opening, hb_string("<%%="))
78
+ && !hb_string_equals(opening, hb_string("<%#")) && !hb_string_equals(opening, hb_string("<%graphql"))) {
79
+ analyzed_ruby_T* analyzed = herb_analyze_ruby(erb_content_node->content->value);
80
+
81
+ erb_content_node->parsed = true;
82
+ erb_content_node->valid = analyzed->valid;
83
+ erb_content_node->analyzed_ruby = analyzed;
84
+
85
+ if (!analyzed->valid && analyzed->unclosed_control_flow_count >= 2) {
86
+ append_erb_multiple_blocks_in_tag_error(
87
+ erb_content_node->base.location.start,
88
+ erb_content_node->base.location.end,
89
+ allocator,
90
+ erb_content_node->base.errors
91
+ );
92
+ }
93
+
94
+ if (options && options->strict && !analyzed->valid && has_inline_case_condition(analyzed)) {
95
+ append_erb_case_with_conditions_error(
96
+ erb_content_node->base.location.start,
97
+ erb_content_node->base.location.end,
98
+ allocator,
99
+ erb_content_node->base.errors
100
+ );
101
+ }
102
+ } else {
103
+ erb_content_node->parsed = false;
104
+ erb_content_node->valid = true;
105
+ erb_content_node->analyzed_ruby = NULL;
106
+ }
107
+ }
108
+
109
+ herb_visit_child_nodes(node, analyze_erb_content, data);
110
+
111
+ return false;
112
+ }
113
+
114
+ static size_t process_block_children(
115
+ AST_NODE_T* node,
116
+ hb_array_T* array,
117
+ size_t index,
118
+ hb_array_T* children_array,
119
+ analyze_ruby_context_T* context,
120
+ control_type_t parent_type
121
+ );
122
+
123
+ static size_t process_subsequent_block(
124
+ AST_NODE_T* node,
125
+ hb_array_T* array,
126
+ size_t index,
127
+ AST_NODE_T** subsequent_out,
128
+ analyze_ruby_context_T* context,
129
+ control_type_t parent_type
130
+ );
131
+
132
+ // --- Helper functions for structure processing ---
133
+
134
+ static bool control_type_matches_any(control_type_t type, const control_type_t* list, size_t count) {
135
+ if (!list) { return false; }
136
+
137
+ for (size_t i = 0; i < count; i++) {
138
+ if (type == list[i]) { return true; }
139
+ }
140
+
141
+ return false;
142
+ }
143
+
144
+ static AST_ERB_CONTENT_NODE_T* get_erb_content_at(hb_array_T* array, size_t index) {
145
+ if (index >= hb_array_size(array)) { return NULL; }
146
+
147
+ AST_NODE_T* node = hb_array_get(array, index);
148
+
149
+ if (!node || node->type != AST_ERB_CONTENT_NODE) { return NULL; }
150
+
151
+ return (AST_ERB_CONTENT_NODE_T*) node;
152
+ }
153
+
154
+ static bool peek_control_type(
155
+ hb_array_T* array,
156
+ size_t index,
157
+ control_type_t* out_type,
158
+ AST_ERB_CONTENT_NODE_T** out_node
159
+ ) {
160
+ AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index);
161
+
162
+ if (!erb_node) { return false; }
163
+
164
+ if (out_type) { *out_type = detect_control_type(erb_node); }
165
+ if (out_node) { *out_node = erb_node; }
166
+
167
+ return true;
168
+ }
169
+
170
+ static void collect_children_until(
171
+ hb_array_T* array,
172
+ size_t* index,
173
+ hb_array_T* destination,
174
+ const control_type_t* stop_types,
175
+ size_t stop_count
176
+ ) {
177
+ while (*index < hb_array_size(array)) {
178
+ AST_NODE_T* child = hb_array_get(array, *index);
179
+
180
+ if (!child) { break; }
181
+
182
+ if (child->type == AST_ERB_CONTENT_NODE) {
183
+ control_type_t child_type = detect_control_type((AST_ERB_CONTENT_NODE_T*) child);
184
+
185
+ if (stop_count > 0 && control_type_matches_any(child_type, stop_types, stop_count)) { break; }
186
+ }
187
+
188
+ hb_array_append(destination, child);
189
+
190
+ (*index)++;
191
+ }
192
+ }
193
+
194
+ static AST_ERB_END_NODE_T* build_end_node(AST_ERB_CONTENT_NODE_T* end_erb, hb_allocator_T* allocator) {
195
+ if (!end_erb) { return NULL; }
196
+
197
+ hb_array_T* end_errors = end_erb->base.errors;
198
+ end_erb->base.errors = NULL;
199
+
200
+ AST_ERB_END_NODE_T* end_node = ast_erb_end_node_init(
201
+ end_erb->tag_opening,
202
+ end_erb->content,
203
+ end_erb->tag_closing,
204
+ end_erb->tag_opening->location.start,
205
+ erb_content_end_position(end_erb),
206
+ end_errors,
207
+ allocator
208
+ );
209
+
210
+ ast_node_free((AST_NODE_T*) end_erb, allocator);
211
+
212
+ return end_node;
213
+ }
214
+
215
+ static AST_ERB_END_NODE_T* consume_end_node(
216
+ hb_array_T* array,
217
+ size_t* index,
218
+ const control_type_t* allowed_types,
219
+ size_t allowed_count,
220
+ hb_allocator_T* allocator
221
+ ) {
222
+ if (allowed_count == 0 || !allowed_types) { return NULL; }
223
+
224
+ AST_ERB_CONTENT_NODE_T* candidate = get_erb_content_at(array, *index);
225
+
226
+ if (!candidate) { return NULL; }
227
+
228
+ control_type_t candidate_type = detect_control_type(candidate);
229
+
230
+ if (!control_type_matches_any(candidate_type, allowed_types, allowed_count)) { return NULL; }
231
+
232
+ (*index)++;
233
+
234
+ return build_end_node(candidate, allocator);
235
+ }
236
+
237
+ // --- Structure processing functions ---
238
+
239
+ static size_t process_case_structure(
240
+ AST_NODE_T* node,
241
+ hb_array_T* array,
242
+ size_t index,
243
+ hb_array_T* output_array,
244
+ analyze_ruby_context_T* context
245
+ );
246
+
247
+ static size_t process_begin_structure(
248
+ AST_NODE_T* node,
249
+ hb_array_T* array,
250
+ size_t index,
251
+ hb_array_T* output_array,
252
+ analyze_ruby_context_T* context
253
+ );
254
+
255
+ static size_t process_generic_structure(
256
+ AST_NODE_T* node,
257
+ hb_array_T* array,
258
+ size_t index,
259
+ hb_array_T* output_array,
260
+ analyze_ruby_context_T* context,
261
+ control_type_t initial_type
262
+ );
263
+
264
+ static size_t process_control_structure(
265
+ AST_NODE_T* node,
266
+ hb_array_T* array,
267
+ size_t index,
268
+ hb_array_T* output_array,
269
+ analyze_ruby_context_T* context,
270
+ control_type_t initial_type
271
+ ) {
272
+ switch (initial_type) {
273
+ case CONTROL_TYPE_CASE:
274
+ case CONTROL_TYPE_CASE_MATCH: return process_case_structure(node, array, index, output_array, context);
275
+
276
+ case CONTROL_TYPE_BEGIN: return process_begin_structure(node, array, index, output_array, context);
277
+
278
+ default: return process_generic_structure(node, array, index, output_array, context, initial_type);
279
+ }
280
+ }
281
+
282
+ static size_t process_case_structure(
283
+ AST_NODE_T* node,
284
+ hb_array_T* array,
285
+ size_t index,
286
+ hb_array_T* output_array,
287
+ analyze_ruby_context_T* context
288
+ ) {
289
+ hb_allocator_T* allocator = context->allocator;
290
+ AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index);
291
+ if (!erb_node) { return index; }
292
+
293
+ hb_array_T* when_conditions = hb_array_init(8, allocator);
294
+ hb_array_T* in_conditions = hb_array_init(8, allocator);
295
+ hb_array_T* non_when_non_in_children = hb_array_init(8, allocator);
296
+
297
+ analyzed_ruby_T* analyzed = erb_node->analyzed_ruby;
298
+ bool has_inline_when = has_case_node(analyzed) && has_when_node(analyzed);
299
+ bool has_inline_in = has_case_match_node(analyzed) && has_in_node(analyzed);
300
+
301
+ index++;
302
+
303
+ const control_type_t prelude_stop[] = { CONTROL_TYPE_WHEN, CONTROL_TYPE_IN, CONTROL_TYPE_END };
304
+ collect_children_until(
305
+ array,
306
+ &index,
307
+ non_when_non_in_children,
308
+ prelude_stop,
309
+ sizeof(prelude_stop) / sizeof(prelude_stop[0])
310
+ );
311
+
312
+ // Create a synthetic when/in node for inline when/in (e.g., <% case variable when "a" %>),
313
+ if (has_inline_when || has_inline_in) {
314
+ hb_array_T* statements = non_when_non_in_children;
315
+ non_when_non_in_children = hb_array_init(8, allocator);
316
+
317
+ position_T start_position =
318
+ erb_node->tag_closing ? erb_node->tag_closing->location.end : erb_node->content->location.end;
319
+ position_T end_position = start_position;
320
+
321
+ if (hb_array_size(statements) > 0) {
322
+ AST_NODE_T* last_child = hb_array_last(statements);
323
+ end_position = last_child->location.end;
324
+ }
325
+
326
+ if (has_inline_when) {
327
+ AST_NODE_T* synthetic_node = (AST_NODE_T*) ast_erb_when_node_init(
328
+ NULL,
329
+ NULL,
330
+ NULL,
331
+ NULL,
332
+ statements,
333
+ start_position,
334
+ end_position,
335
+ hb_array_init(0, allocator),
336
+ allocator
337
+ );
338
+
339
+ hb_array_append(when_conditions, synthetic_node);
340
+ } else {
341
+ AST_NODE_T* synthetic_node = (AST_NODE_T*) ast_erb_in_node_init(
342
+ NULL,
343
+ NULL,
344
+ NULL,
345
+ NULL,
346
+ statements,
347
+ start_position,
348
+ end_position,
349
+ hb_array_init(0, allocator),
350
+ allocator
351
+ );
352
+
353
+ hb_array_append(in_conditions, synthetic_node);
354
+ }
355
+ }
356
+
357
+ while (index < hb_array_size(array)) {
358
+ AST_ERB_CONTENT_NODE_T* next_erb = get_erb_content_at(array, index);
359
+
360
+ if (!next_erb) {
361
+ AST_NODE_T* next_node = hb_array_get(array, index);
362
+
363
+ if (!next_node) { break; }
364
+
365
+ hb_array_append(non_when_non_in_children, next_node);
366
+ index++;
367
+ continue;
368
+ }
369
+
370
+ control_type_t next_type = detect_control_type(next_erb);
371
+
372
+ if (next_type == CONTROL_TYPE_WHEN || next_type == CONTROL_TYPE_IN) {
373
+ hb_array_T* statements = hb_array_init(8, allocator);
374
+ index++;
375
+ index = process_block_children(node, array, index, statements, context, next_type);
376
+
377
+ hb_array_T* cond_errors = next_erb->base.errors;
378
+ next_erb->base.errors = NULL;
379
+
380
+ location_T* then_keyword = compute_then_keyword(next_erb, next_type, allocator);
381
+ position_T cond_start = next_erb->tag_opening->location.start;
382
+ position_T cond_end = erb_content_end_position(next_erb);
383
+
384
+ AST_NODE_T* condition_node;
385
+
386
+ if (next_type == CONTROL_TYPE_WHEN) {
387
+ condition_node = (AST_NODE_T*) ast_erb_when_node_init(
388
+ next_erb->tag_opening,
389
+ next_erb->content,
390
+ next_erb->tag_closing,
391
+ then_keyword,
392
+ statements,
393
+ cond_start,
394
+ cond_end,
395
+ cond_errors,
396
+ allocator
397
+ );
398
+ } else {
399
+ condition_node = (AST_NODE_T*) ast_erb_in_node_init(
400
+ next_erb->tag_opening,
401
+ next_erb->content,
402
+ next_erb->tag_closing,
403
+ then_keyword,
404
+ statements,
405
+ cond_start,
406
+ cond_end,
407
+ cond_errors,
408
+ allocator
409
+ );
410
+ }
411
+
412
+ ast_node_free((AST_NODE_T*) next_erb, allocator);
413
+ hb_array_append(next_type == CONTROL_TYPE_WHEN ? when_conditions : in_conditions, condition_node);
414
+ continue;
415
+ }
416
+
417
+ if (next_type == CONTROL_TYPE_ELSE || next_type == CONTROL_TYPE_END) { break; }
418
+
419
+ hb_array_append(non_when_non_in_children, (AST_NODE_T*) next_erb);
420
+ index++;
421
+ }
422
+
423
+ AST_ERB_ELSE_NODE_T* else_clause = NULL;
424
+ control_type_t next_type = CONTROL_TYPE_UNKNOWN;
425
+ AST_ERB_CONTENT_NODE_T* next_erb = NULL;
426
+
427
+ if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_ELSE) {
428
+ hb_array_T* else_children = hb_array_init(8, allocator);
429
+ index++;
430
+
431
+ index = process_block_children(node, array, index, else_children, context, CONTROL_TYPE_CASE);
432
+
433
+ hb_array_T* else_errors = next_erb->base.errors;
434
+ next_erb->base.errors = NULL;
435
+
436
+ else_clause = ast_erb_else_node_init(
437
+ next_erb->tag_opening,
438
+ next_erb->content,
439
+ next_erb->tag_closing,
440
+ else_children,
441
+ next_erb->tag_opening->location.start,
442
+ erb_content_end_position(next_erb),
443
+ else_errors,
444
+ allocator
445
+ );
446
+
447
+ ast_node_free((AST_NODE_T*) next_erb, allocator);
448
+ }
449
+
450
+ const control_type_t end_types[] = { CONTROL_TYPE_END };
451
+ AST_ERB_END_NODE_T* end_node =
452
+ consume_end_node(array, &index, end_types, sizeof(end_types) / sizeof(end_types[0]), allocator);
453
+
454
+ position_T start_position = erb_node->tag_opening->location.start;
455
+ position_T end_position = erb_content_end_position(erb_node);
456
+
457
+ if (end_node) {
458
+ end_position = end_node->base.location.end;
459
+ } else if (else_clause) {
460
+ end_position = else_clause->base.location.end;
461
+ } else if (hb_array_size(when_conditions) > 0) {
462
+ AST_NODE_T* last_when = hb_array_last(when_conditions);
463
+ end_position = last_when->location.end;
464
+ } else if (hb_array_size(in_conditions) > 0) {
465
+ AST_NODE_T* last_in = hb_array_last(in_conditions);
466
+ end_position = last_in->location.end;
467
+ }
468
+
469
+ hb_array_T* node_errors = erb_node->base.errors;
470
+ erb_node->base.errors = NULL;
471
+
472
+ if (hb_array_size(in_conditions) > 0) {
473
+ AST_ERB_CASE_MATCH_NODE_T* case_match_node = ast_erb_case_match_node_init(
474
+ erb_node->tag_opening,
475
+ erb_node->content,
476
+ erb_node->tag_closing,
477
+ non_when_non_in_children,
478
+ HERB_PRISM_NODE_EMPTY,
479
+ in_conditions,
480
+ else_clause,
481
+ end_node,
482
+ start_position,
483
+ end_position,
484
+ node_errors,
485
+ allocator
486
+ );
487
+
488
+ ast_node_free((AST_NODE_T*) erb_node, allocator);
489
+ hb_array_append(output_array, (AST_NODE_T*) case_match_node);
490
+ hb_array_free(&when_conditions);
491
+ return index;
492
+ }
493
+
494
+ AST_ERB_CASE_NODE_T* case_node = ast_erb_case_node_init(
495
+ erb_node->tag_opening,
496
+ erb_node->content,
497
+ erb_node->tag_closing,
498
+ non_when_non_in_children,
499
+ HERB_PRISM_NODE_EMPTY,
500
+ when_conditions,
501
+ else_clause,
502
+ end_node,
503
+ start_position,
504
+ end_position,
505
+ node_errors,
506
+ allocator
507
+ );
508
+
509
+ ast_node_free((AST_NODE_T*) erb_node, allocator);
510
+ hb_array_append(output_array, (AST_NODE_T*) case_node);
511
+ hb_array_free(&in_conditions);
512
+
513
+ return index;
514
+ }
515
+
516
+ static size_t process_begin_structure(
517
+ AST_NODE_T* node,
518
+ hb_array_T* array,
519
+ size_t index,
520
+ hb_array_T* output_array,
521
+ analyze_ruby_context_T* context
522
+ ) {
523
+ hb_allocator_T* allocator = context->allocator;
524
+ AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index);
525
+ if (!erb_node) { return index; }
526
+ hb_array_T* children = hb_array_init(8, allocator);
527
+
528
+ index++;
529
+ index = process_block_children(node, array, index, children, context, CONTROL_TYPE_BEGIN);
530
+
531
+ AST_ERB_RESCUE_NODE_T* rescue_clause = NULL;
532
+ AST_ERB_ELSE_NODE_T* else_clause = NULL;
533
+ AST_ERB_ENSURE_NODE_T* ensure_clause = NULL;
534
+
535
+ control_type_t next_type = CONTROL_TYPE_UNKNOWN;
536
+ AST_ERB_CONTENT_NODE_T* next_erb = NULL;
537
+
538
+ if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_RESCUE) {
539
+ AST_NODE_T* rescue_node = NULL;
540
+ index = process_subsequent_block(node, array, index, &rescue_node, context, CONTROL_TYPE_BEGIN);
541
+ rescue_clause = (AST_ERB_RESCUE_NODE_T*) rescue_node;
542
+ }
543
+
544
+ if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_ELSE) {
545
+ hb_array_T* else_children = hb_array_init(8, allocator);
546
+ index++;
547
+
548
+ index = process_block_children(node, array, index, else_children, context, CONTROL_TYPE_BEGIN);
549
+
550
+ hb_array_T* else_errors = next_erb->base.errors;
551
+ next_erb->base.errors = NULL;
552
+
553
+ else_clause = ast_erb_else_node_init(
554
+ next_erb->tag_opening,
555
+ next_erb->content,
556
+ next_erb->tag_closing,
557
+ else_children,
558
+ next_erb->tag_opening->location.start,
559
+ erb_content_end_position(next_erb),
560
+ else_errors,
561
+ allocator
562
+ );
563
+
564
+ ast_node_free((AST_NODE_T*) next_erb, allocator);
565
+ }
566
+
567
+ if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_ENSURE) {
568
+ hb_array_T* ensure_children = hb_array_init(8, allocator);
569
+ index++;
570
+
571
+ const control_type_t ensure_stop[] = { CONTROL_TYPE_END };
572
+ collect_children_until(array, &index, ensure_children, ensure_stop, sizeof(ensure_stop) / sizeof(ensure_stop[0]));
573
+
574
+ hb_array_T* ensure_errors = next_erb->base.errors;
575
+ next_erb->base.errors = NULL;
576
+
577
+ ensure_clause = ast_erb_ensure_node_init(
578
+ next_erb->tag_opening,
579
+ next_erb->content,
580
+ next_erb->tag_closing,
581
+ ensure_children,
582
+ next_erb->tag_opening->location.start,
583
+ erb_content_end_position(next_erb),
584
+ ensure_errors,
585
+ allocator
586
+ );
587
+
588
+ ast_node_free((AST_NODE_T*) next_erb, allocator);
589
+ }
590
+
591
+ const control_type_t end_types[] = { CONTROL_TYPE_END };
592
+ AST_ERB_END_NODE_T* end_node =
593
+ consume_end_node(array, &index, end_types, sizeof(end_types) / sizeof(end_types[0]), allocator);
594
+
595
+ position_T start_position = erb_node->tag_opening->location.start;
596
+ position_T end_position = erb_content_end_position(erb_node);
597
+
598
+ if (end_node) {
599
+ end_position = end_node->base.location.end;
600
+ } else if (ensure_clause) {
601
+ end_position = ensure_clause->base.location.end;
602
+ } else if (else_clause) {
603
+ end_position = else_clause->base.location.end;
604
+ } else if (rescue_clause) {
605
+ end_position = rescue_clause->base.location.end;
606
+ }
607
+
608
+ hb_array_T* begin_errors = erb_node->base.errors;
609
+ erb_node->base.errors = NULL;
610
+
611
+ AST_ERB_BEGIN_NODE_T* begin_node = ast_erb_begin_node_init(
612
+ erb_node->tag_opening,
613
+ erb_node->content,
614
+ erb_node->tag_closing,
615
+ HERB_PRISM_NODE_EMPTY,
616
+ children,
617
+ rescue_clause,
618
+ else_clause,
619
+ ensure_clause,
620
+ end_node,
621
+ start_position,
622
+ end_position,
623
+ begin_errors,
624
+ allocator
625
+ );
626
+
627
+ ast_node_free((AST_NODE_T*) erb_node, allocator);
628
+ hb_array_append(output_array, (AST_NODE_T*) begin_node);
629
+
630
+ return index;
631
+ }
632
+
633
+ static size_t process_generic_structure(
634
+ AST_NODE_T* node,
635
+ hb_array_T* array,
636
+ size_t index,
637
+ hb_array_T* output_array,
638
+ analyze_ruby_context_T* context,
639
+ control_type_t initial_type
640
+ ) {
641
+ hb_allocator_T* allocator = context->allocator;
642
+ AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index);
643
+ if (!erb_node) { return index; }
644
+ hb_array_T* children = hb_array_init(8, allocator);
645
+
646
+ index++;
647
+ index = process_block_children(node, array, index, children, context, initial_type);
648
+
649
+ AST_NODE_T* subsequent = NULL;
650
+ control_type_t next_type = CONTROL_TYPE_UNKNOWN;
651
+
652
+ if (peek_control_type(array, index, &next_type, NULL) && is_subsequent_type(initial_type, next_type)) {
653
+ index = process_subsequent_block(node, array, index, &subsequent, context, initial_type);
654
+ }
655
+
656
+ AST_ERB_END_NODE_T* end_node = NULL;
657
+
658
+ if (initial_type == CONTROL_TYPE_BLOCK) {
659
+ const control_type_t block_end_types[] = { CONTROL_TYPE_BLOCK_CLOSE, CONTROL_TYPE_END };
660
+ end_node =
661
+ consume_end_node(array, &index, block_end_types, sizeof(block_end_types) / sizeof(block_end_types[0]), allocator);
662
+ } else {
663
+ const control_type_t default_end_types[] = { CONTROL_TYPE_END };
664
+ end_node = consume_end_node(
665
+ array,
666
+ &index,
667
+ default_end_types,
668
+ sizeof(default_end_types) / sizeof(default_end_types[0]),
669
+ allocator
670
+ );
671
+ }
672
+
673
+ AST_NODE_T* control_node = create_control_node(erb_node, children, subsequent, end_node, initial_type, allocator);
674
+
675
+ if (control_node) {
676
+ ast_node_free((AST_NODE_T*) erb_node, allocator);
677
+ hb_array_append(output_array, control_node);
678
+ } else {
679
+ hb_array_free(&children);
680
+ }
681
+
682
+ return index;
683
+ }
684
+
685
+ static size_t process_subsequent_block(
686
+ AST_NODE_T* node,
687
+ hb_array_T* array,
688
+ size_t index,
689
+ AST_NODE_T** subsequent_out,
690
+ analyze_ruby_context_T* context,
691
+ control_type_t parent_type
692
+ ) {
693
+ hb_allocator_T* allocator = context->allocator;
694
+ AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index);
695
+
696
+ if (!erb_node) { return index; }
697
+
698
+ control_type_t type = detect_control_type(erb_node);
699
+ hb_array_T* children = hb_array_init(8, allocator);
700
+
701
+ index++;
702
+
703
+ index = process_block_children(node, array, index, children, context, parent_type);
704
+
705
+ AST_NODE_T* subsequent_node = create_control_node(erb_node, children, NULL, NULL, type, allocator);
706
+
707
+ if (subsequent_node) {
708
+ ast_node_free((AST_NODE_T*) erb_node, allocator);
709
+ } else {
710
+ hb_array_free(&children);
711
+ }
712
+
713
+ control_type_t next_type = CONTROL_TYPE_UNKNOWN;
714
+
715
+ if (peek_control_type(array, index, &next_type, NULL) && is_subsequent_type(parent_type, next_type)
716
+ && !(type == CONTROL_TYPE_RESCUE && (next_type == CONTROL_TYPE_ELSE || next_type == CONTROL_TYPE_ENSURE))) {
717
+
718
+ AST_NODE_T** next_subsequent = NULL;
719
+
720
+ switch (type) {
721
+ case CONTROL_TYPE_ELSIF: {
722
+ if (subsequent_node && subsequent_node->type == AST_ERB_IF_NODE) {
723
+ next_subsequent = &(((AST_ERB_IF_NODE_T*) subsequent_node)->subsequent);
724
+ }
725
+
726
+ break;
727
+ }
728
+
729
+ case CONTROL_TYPE_RESCUE: {
730
+ if (subsequent_node && subsequent_node->type == AST_ERB_RESCUE_NODE && next_type == CONTROL_TYPE_RESCUE) {
731
+ AST_NODE_T* next_rescue_node = NULL;
732
+ index = process_subsequent_block(node, array, index, &next_rescue_node, context, parent_type);
733
+
734
+ if (next_rescue_node) {
735
+ ((AST_ERB_RESCUE_NODE_T*) subsequent_node)->subsequent = (AST_ERB_RESCUE_NODE_T*) next_rescue_node;
736
+ }
737
+
738
+ next_subsequent = NULL;
739
+ }
740
+
741
+ break;
742
+ }
743
+
744
+ default: break;
745
+ }
746
+
747
+ if (next_subsequent) {
748
+ index = process_subsequent_block(node, array, index, next_subsequent, context, parent_type);
749
+ }
750
+ }
751
+
752
+ *subsequent_out = subsequent_node;
753
+ return index;
754
+ }
755
+
756
+ static size_t process_block_children(
757
+ AST_NODE_T* node,
758
+ hb_array_T* array,
759
+ size_t index,
760
+ hb_array_T* children_array,
761
+ analyze_ruby_context_T* context,
762
+ control_type_t parent_type
763
+ ) {
764
+ while (index < hb_array_size(array)) {
765
+ AST_NODE_T* child = hb_array_get(array, index);
766
+
767
+ if (!child) { break; }
768
+
769
+ if (child->type != AST_ERB_CONTENT_NODE) {
770
+ hb_array_append(children_array, child);
771
+ index++;
772
+ continue;
773
+ }
774
+
775
+ AST_ERB_CONTENT_NODE_T* erb_content = (AST_ERB_CONTENT_NODE_T*) child;
776
+ control_type_t child_type = detect_control_type(erb_content);
777
+
778
+ if (is_terminator_type(parent_type, child_type)) { break; }
779
+
780
+ if (is_compound_control_type(child_type)) {
781
+ hb_array_T* temp_array = hb_array_init(1, context->allocator);
782
+ size_t new_index = process_control_structure(node, array, index, temp_array, context, child_type);
783
+
784
+ if (hb_array_size(temp_array) > 0) { hb_array_append(children_array, hb_array_first(temp_array)); }
785
+
786
+ hb_array_free(&temp_array);
787
+
788
+ index = new_index;
789
+ continue;
790
+ }
791
+
792
+ hb_array_append(children_array, child);
793
+ index++;
794
+ }
795
+
796
+ return index;
797
+ }
798
+
799
+ hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context) {
800
+ hb_allocator_T* allocator = context->allocator;
801
+ hb_array_T* new_array = hb_array_init(hb_array_size(array), allocator);
802
+ size_t index = 0;
803
+
804
+ while (index < hb_array_size(array)) {
805
+ AST_NODE_T* item = hb_array_get(array, index);
806
+
807
+ if (!item) { break; }
808
+
809
+ if (item->type != AST_ERB_CONTENT_NODE) {
810
+ hb_array_append(new_array, item);
811
+ index++;
812
+ continue;
813
+ }
814
+
815
+ AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) item;
816
+ control_type_t type = detect_control_type(erb_node);
817
+
818
+ if (is_compound_control_type(type)) {
819
+ index = process_control_structure(node, array, index, new_array, context, type);
820
+ continue;
821
+ }
822
+
823
+ if (type == CONTROL_TYPE_YIELD) {
824
+ AST_NODE_T* yield_node = create_control_node(erb_node, NULL, NULL, NULL, type, allocator);
825
+
826
+ if (yield_node) {
827
+ ast_node_free((AST_NODE_T*) erb_node, allocator);
828
+ hb_array_append(new_array, yield_node);
829
+ } else {
830
+ hb_array_append(new_array, item);
831
+ }
832
+
833
+ index++;
834
+ continue;
835
+ }
836
+
837
+ hb_array_append(new_array, item);
838
+ index++;
839
+ }
840
+
841
+ return new_array;
842
+ }
843
+
844
+ void herb_analyze_parse_tree(
845
+ AST_DOCUMENT_NODE_T* document,
846
+ const char* source,
847
+ const parser_options_T* options,
848
+ hb_allocator_T* allocator
849
+ ) {
850
+ analyze_erb_content_context_T erb_ctx = { .options = options, .allocator = allocator };
851
+ herb_visit_node((AST_NODE_T*) document, analyze_erb_content, (void*) &erb_ctx);
852
+
853
+ analyze_ruby_context_T context = {
854
+ .document = document,
855
+ .parent = NULL,
856
+ .ruby_context_stack = hb_array_init(8, allocator),
857
+ .allocator = allocator,
858
+ .source = source,
859
+ };
860
+
861
+ herb_visit_node((AST_NODE_T*) document, transform_erb_nodes, &context);
862
+
863
+ if (options && options->render_nodes) { herb_visit_node((AST_NODE_T*) document, transform_render_nodes, &context); }
864
+
865
+ if (options && options->action_view_helpers) {
866
+ herb_visit_node((AST_NODE_T*) document, transform_tag_helper_nodes, &context);
867
+ }
868
+
869
+ herb_transform_conditional_elements(document, allocator);
870
+ herb_transform_conditional_open_tags(document, allocator);
871
+
872
+ invalid_erb_context_T invalid_context = {
873
+ .loop_depth = 0,
874
+ .rescue_depth = 0,
875
+ .allocator = allocator,
876
+ };
877
+
878
+ herb_visit_node((AST_NODE_T*) document, detect_invalid_erb_structures, &invalid_context);
879
+
880
+ herb_analyze_parse_errors(document, source, allocator);
881
+
882
+ herb_parser_match_html_tags_post_analyze(document, options, allocator);
883
+
884
+ hb_array_free(&context.ruby_context_stack);
885
+ }