@herb-tools/node 0.8.10 → 0.9.0

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