@herb-tools/node 0.8.10 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/binding.gyp +27 -8
  2. package/dist/herb-node.cjs +41 -12
  3. package/dist/herb-node.cjs.map +1 -1
  4. package/dist/herb-node.esm.js +8 -1
  5. package/dist/herb-node.esm.js.map +1 -1
  6. package/dist/types/node-backend.d.ts +3 -1
  7. package/extension/error_helpers.cpp +598 -73
  8. package/extension/error_helpers.h +20 -3
  9. package/extension/extension_helpers.cpp +40 -35
  10. package/extension/extension_helpers.h +2 -2
  11. package/extension/herb.cpp +194 -64
  12. package/extension/libherb/analyze/action_view/attribute_extraction_helpers.c +303 -0
  13. package/extension/libherb/analyze/action_view/attribute_extraction_helpers.h +36 -0
  14. package/extension/libherb/analyze/action_view/content_tag.c +78 -0
  15. package/extension/libherb/analyze/action_view/link_to.c +167 -0
  16. package/extension/libherb/analyze/action_view/registry.c +83 -0
  17. package/extension/libherb/analyze/action_view/tag.c +70 -0
  18. package/extension/libherb/analyze/action_view/tag_helper_handler.h +43 -0
  19. package/extension/libherb/analyze/action_view/tag_helper_node_builders.c +305 -0
  20. package/extension/libherb/analyze/action_view/tag_helper_node_builders.h +70 -0
  21. package/extension/libherb/analyze/action_view/tag_helpers.c +815 -0
  22. package/extension/libherb/analyze/action_view/tag_helpers.h +38 -0
  23. package/extension/libherb/analyze/action_view/turbo_frame_tag.c +88 -0
  24. package/extension/libherb/analyze/analyze.c +885 -0
  25. package/extension/libherb/{include → analyze}/analyze.h +14 -4
  26. package/extension/libherb/{analyzed_ruby.c → analyze/analyzed_ruby.c} +13 -11
  27. package/extension/libherb/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
  28. package/extension/libherb/analyze/builders.c +343 -0
  29. package/extension/libherb/analyze/builders.h +27 -0
  30. package/extension/libherb/analyze/conditional_elements.c +594 -0
  31. package/extension/libherb/analyze/conditional_elements.h +9 -0
  32. package/extension/libherb/analyze/conditional_open_tags.c +640 -0
  33. package/extension/libherb/analyze/conditional_open_tags.h +9 -0
  34. package/extension/libherb/analyze/control_type.c +250 -0
  35. package/extension/libherb/analyze/control_type.h +14 -0
  36. package/extension/libherb/{analyze_helpers.c → analyze/helpers.c} +48 -23
  37. package/extension/libherb/{analyze_helpers.h → analyze/helpers.h} +4 -2
  38. package/extension/libherb/analyze/invalid_structures.c +193 -0
  39. package/extension/libherb/analyze/invalid_structures.h +11 -0
  40. package/extension/libherb/{analyze_missing_end.c → analyze/missing_end.c} +33 -22
  41. package/extension/libherb/analyze/parse_errors.c +84 -0
  42. package/extension/libherb/analyze/prism_annotate.c +399 -0
  43. package/extension/libherb/analyze/prism_annotate.h +16 -0
  44. package/extension/libherb/analyze/render_nodes.c +761 -0
  45. package/extension/libherb/analyze/render_nodes.h +11 -0
  46. package/extension/libherb/{analyze_transform.c → analyze/transform.c} +24 -3
  47. package/extension/libherb/ast_node.c +17 -7
  48. package/extension/libherb/ast_node.h +11 -5
  49. package/extension/libherb/ast_nodes.c +760 -388
  50. package/extension/libherb/ast_nodes.h +155 -39
  51. package/extension/libherb/ast_pretty_print.c +265 -7
  52. package/extension/libherb/ast_pretty_print.h +6 -1
  53. package/extension/libherb/element_source.h +3 -8
  54. package/extension/libherb/errors.c +1455 -520
  55. package/extension/libherb/errors.h +207 -56
  56. package/extension/libherb/extract.c +145 -49
  57. package/extension/libherb/extract.h +21 -5
  58. package/extension/libherb/herb.c +52 -34
  59. package/extension/libherb/herb.h +18 -6
  60. package/extension/libherb/herb_prism_node.h +13 -0
  61. package/extension/libherb/html_util.c +241 -12
  62. package/extension/libherb/html_util.h +7 -2
  63. package/extension/libherb/include/analyze/action_view/attribute_extraction_helpers.h +36 -0
  64. package/extension/libherb/include/analyze/action_view/tag_helper_handler.h +43 -0
  65. package/extension/libherb/include/analyze/action_view/tag_helper_node_builders.h +70 -0
  66. package/extension/libherb/include/analyze/action_view/tag_helpers.h +38 -0
  67. package/extension/libherb/{analyze.h → include/analyze/analyze.h} +14 -4
  68. package/extension/libherb/include/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
  69. package/extension/libherb/include/analyze/builders.h +27 -0
  70. package/extension/libherb/include/analyze/conditional_elements.h +9 -0
  71. package/extension/libherb/include/analyze/conditional_open_tags.h +9 -0
  72. package/extension/libherb/include/analyze/control_type.h +14 -0
  73. package/extension/libherb/include/{analyze_helpers.h → analyze/helpers.h} +4 -2
  74. package/extension/libherb/include/analyze/invalid_structures.h +11 -0
  75. package/extension/libherb/include/analyze/prism_annotate.h +16 -0
  76. package/extension/libherb/include/analyze/render_nodes.h +11 -0
  77. package/extension/libherb/include/ast_node.h +11 -5
  78. package/extension/libherb/include/ast_nodes.h +155 -39
  79. package/extension/libherb/include/ast_pretty_print.h +6 -1
  80. package/extension/libherb/include/element_source.h +3 -8
  81. package/extension/libherb/include/errors.h +207 -56
  82. package/extension/libherb/include/extract.h +21 -5
  83. package/extension/libherb/include/herb.h +18 -6
  84. package/extension/libherb/include/herb_prism_node.h +13 -0
  85. package/extension/libherb/include/html_util.h +7 -2
  86. package/extension/libherb/include/io.h +3 -1
  87. package/extension/libherb/include/lex_helpers.h +29 -0
  88. package/extension/libherb/include/lexer.h +1 -1
  89. package/extension/libherb/include/lexer_peek_helpers.h +87 -13
  90. package/extension/libherb/include/lexer_struct.h +2 -0
  91. package/extension/libherb/include/location.h +2 -1
  92. package/extension/libherb/include/parser.h +28 -2
  93. package/extension/libherb/include/parser_helpers.h +19 -3
  94. package/extension/libherb/include/pretty_print.h +10 -5
  95. package/extension/libherb/include/prism_context.h +45 -0
  96. package/extension/libherb/include/prism_helpers.h +10 -7
  97. package/extension/libherb/include/prism_serialized.h +12 -0
  98. package/extension/libherb/include/token.h +16 -4
  99. package/extension/libherb/include/token_struct.h +10 -3
  100. package/extension/libherb/include/utf8.h +2 -1
  101. package/extension/libherb/include/util/hb_allocator.h +78 -0
  102. package/extension/libherb/include/util/hb_arena.h +6 -1
  103. package/extension/libherb/include/util/hb_arena_debug.h +12 -1
  104. package/extension/libherb/include/util/hb_array.h +7 -3
  105. package/extension/libherb/include/util/hb_buffer.h +6 -4
  106. package/extension/libherb/include/util/hb_foreach.h +79 -0
  107. package/extension/libherb/include/util/hb_narray.h +8 -4
  108. package/extension/libherb/include/util/hb_string.h +56 -9
  109. package/extension/libherb/include/util.h +6 -3
  110. package/extension/libherb/include/version.h +1 -1
  111. package/extension/libherb/io.c +3 -2
  112. package/extension/libherb/io.h +3 -1
  113. package/extension/libherb/lex_helpers.h +29 -0
  114. package/extension/libherb/lexer.c +42 -30
  115. package/extension/libherb/lexer.h +1 -1
  116. package/extension/libherb/lexer_peek_helpers.c +12 -74
  117. package/extension/libherb/lexer_peek_helpers.h +87 -13
  118. package/extension/libherb/lexer_struct.h +2 -0
  119. package/extension/libherb/location.c +2 -2
  120. package/extension/libherb/location.h +2 -1
  121. package/extension/libherb/main.c +53 -28
  122. package/extension/libherb/parser.c +784 -247
  123. package/extension/libherb/parser.h +28 -2
  124. package/extension/libherb/parser_helpers.c +110 -23
  125. package/extension/libherb/parser_helpers.h +19 -3
  126. package/extension/libherb/parser_match_tags.c +130 -49
  127. package/extension/libherb/pretty_print.c +29 -24
  128. package/extension/libherb/pretty_print.h +10 -5
  129. package/extension/libherb/prism_context.h +45 -0
  130. package/extension/libherb/prism_helpers.c +30 -27
  131. package/extension/libherb/prism_helpers.h +10 -7
  132. package/extension/libherb/prism_serialized.h +12 -0
  133. package/extension/libherb/ruby_parser.c +2 -0
  134. package/extension/libherb/token.c +151 -66
  135. package/extension/libherb/token.h +16 -4
  136. package/extension/libherb/token_matchers.c +0 -1
  137. package/extension/libherb/token_struct.h +10 -3
  138. package/extension/libherb/utf8.c +7 -6
  139. package/extension/libherb/utf8.h +2 -1
  140. package/extension/libherb/util/hb_allocator.c +341 -0
  141. package/extension/libherb/util/hb_allocator.h +78 -0
  142. package/extension/libherb/util/hb_arena.c +81 -56
  143. package/extension/libherb/util/hb_arena.h +6 -1
  144. package/extension/libherb/util/hb_arena_debug.c +32 -17
  145. package/extension/libherb/util/hb_arena_debug.h +12 -1
  146. package/extension/libherb/util/hb_array.c +30 -15
  147. package/extension/libherb/util/hb_array.h +7 -3
  148. package/extension/libherb/util/hb_buffer.c +17 -21
  149. package/extension/libherb/util/hb_buffer.h +6 -4
  150. package/extension/libherb/util/hb_foreach.h +79 -0
  151. package/extension/libherb/util/hb_narray.c +22 -7
  152. package/extension/libherb/util/hb_narray.h +8 -4
  153. package/extension/libherb/util/hb_string.c +49 -35
  154. package/extension/libherb/util/hb_string.h +56 -9
  155. package/extension/libherb/util.c +21 -11
  156. package/extension/libherb/util.h +6 -3
  157. package/extension/libherb/version.h +1 -1
  158. package/extension/libherb/visitor.c +68 -1
  159. package/extension/nodes.cpp +593 -6
  160. package/extension/nodes.h +10 -1
  161. package/package.json +12 -8
  162. package/src/node-backend.ts +11 -1
  163. package/dist/types/index-cjs.d.cts +0 -1
  164. package/extension/libherb/analyze.c +0 -1608
  165. package/extension/libherb/element_source.c +0 -12
  166. package/extension/libherb/include/util/hb_system.h +0 -9
  167. package/extension/libherb/util/hb_system.c +0 -30
  168. package/extension/libherb/util/hb_system.h +0 -9
  169. package/src/index-cjs.cts +0 -22
  170. /package/dist/types/{index-esm.d.mts → index.d.ts} +0 -0
  171. /package/src/{index-esm.mts → index.ts} +0 -0
@@ -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
+ }