@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,640 @@
1
+ #include "../include/analyze/conditional_open_tags.h"
2
+ #include "../include/ast_nodes.h"
3
+ #include "../include/element_source.h"
4
+ #include "../include/errors.h"
5
+ #include "../include/token_struct.h"
6
+ #include "../include/util/hb_allocator.h"
7
+ #include "../include/util/hb_array.h"
8
+ #include "../include/util/hb_string.h"
9
+ #include "../include/visitor.h"
10
+
11
+ #include <stdbool.h>
12
+ #include <stdlib.h>
13
+ #include <string.h>
14
+
15
+ typedef struct {
16
+ hb_array_T* document_errors;
17
+ hb_allocator_T* allocator;
18
+ } conditional_open_tags_context_T;
19
+
20
+ static bool transform_conditional_open_tags_visitor(const AST_NODE_T* node, void* data);
21
+ static void transform_conditional_open_tags_in_array(hb_array_T* array, conditional_open_tags_context_T* context);
22
+
23
+ static bool is_non_void_open_tag(AST_NODE_T* node) {
24
+ if (!node || node->type != AST_HTML_OPEN_TAG_NODE) { return false; }
25
+
26
+ AST_HTML_OPEN_TAG_NODE_T* open_tag = (AST_HTML_OPEN_TAG_NODE_T*) node;
27
+
28
+ return !open_tag->is_void;
29
+ }
30
+
31
+ static hb_string_T get_open_tag_name(AST_HTML_OPEN_TAG_NODE_T* open_tag) {
32
+ if (!open_tag || !open_tag->tag_name) { return HB_STRING_NULL; }
33
+
34
+ return open_tag->tag_name->value;
35
+ }
36
+
37
+ typedef struct {
38
+ AST_HTML_OPEN_TAG_NODE_T* tag;
39
+ AST_HTML_OPEN_TAG_NODE_T* second_tag;
40
+ bool has_multiple_tags;
41
+ } single_open_tag_result_T;
42
+
43
+ static bool has_matching_close_tag_in_statements(hb_array_T* statements, size_t open_tag_index, hb_string_T tag_name) {
44
+ if (!statements || hb_string_is_empty(tag_name)) { return false; }
45
+
46
+ int depth = 0;
47
+
48
+ for (size_t i = open_tag_index + 1; i < hb_array_size(statements); i++) {
49
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(statements, i);
50
+
51
+ if (!node) { continue; }
52
+
53
+ if (node->type == AST_HTML_OPEN_TAG_NODE) {
54
+ AST_HTML_OPEN_TAG_NODE_T* open_tag = (AST_HTML_OPEN_TAG_NODE_T*) node;
55
+
56
+ if (open_tag->tag_name && !hb_string_is_empty(open_tag->tag_name->value)) {
57
+ if (hb_string_equals_case_insensitive(tag_name, open_tag->tag_name->value)) { depth++; }
58
+ }
59
+ } else if (node->type == AST_HTML_CLOSE_TAG_NODE) {
60
+ AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node;
61
+
62
+ if (close_tag->tag_name && !hb_string_is_empty(close_tag->tag_name->value)) {
63
+ if (hb_string_equals_case_insensitive(tag_name, close_tag->tag_name->value)) {
64
+ if (depth == 0) { return true; }
65
+ depth--;
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ return false;
72
+ }
73
+
74
+ static single_open_tag_result_T get_single_open_tag_from_statements(hb_array_T* statements) {
75
+ single_open_tag_result_T result = { .tag = NULL, .second_tag = NULL, .has_multiple_tags = false };
76
+
77
+ if (!statements || hb_array_size(statements) == 0) { return result; }
78
+
79
+ size_t tag_count = 0;
80
+ size_t first_tag_index = 0;
81
+
82
+ for (size_t i = 0; i < hb_array_size(statements); i++) {
83
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(statements, i);
84
+
85
+ if (!node) { continue; }
86
+
87
+ if (node->type == AST_HTML_TEXT_NODE) {
88
+ AST_HTML_TEXT_NODE_T* text = (AST_HTML_TEXT_NODE_T*) node;
89
+ if (hb_string_is_blank(text->content)) { continue; }
90
+
91
+ if (result.tag) {
92
+ hb_string_T tag_name = get_open_tag_name(result.tag);
93
+
94
+ if (!hb_string_is_empty(tag_name)
95
+ && has_matching_close_tag_in_statements(statements, first_tag_index, tag_name)) {
96
+ result.tag = NULL;
97
+ result.has_multiple_tags = false;
98
+ result.second_tag = NULL;
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+
104
+ if (is_non_void_open_tag(node)) {
105
+ tag_count++;
106
+
107
+ if (tag_count == 1) {
108
+ result.tag = (AST_HTML_OPEN_TAG_NODE_T*) node;
109
+ first_tag_index = i;
110
+ } else if (tag_count == 2) {
111
+ result.second_tag = (AST_HTML_OPEN_TAG_NODE_T*) node;
112
+ result.has_multiple_tags = true;
113
+ }
114
+
115
+ continue;
116
+ }
117
+ }
118
+
119
+ if (tag_count != 1) {
120
+ result.tag = NULL;
121
+
122
+ if (result.has_multiple_tags && result.second_tag) {
123
+ hb_string_T first_tag_name =
124
+ get_open_tag_name((AST_HTML_OPEN_TAG_NODE_T*) hb_array_get(statements, first_tag_index));
125
+ bool first_has_close = !hb_string_is_empty(first_tag_name)
126
+ && has_matching_close_tag_in_statements(statements, first_tag_index, first_tag_name);
127
+
128
+ if (first_has_close) {
129
+ result.has_multiple_tags = false;
130
+ result.second_tag = NULL;
131
+ }
132
+ }
133
+ }
134
+
135
+ if (result.tag) {
136
+ hb_string_T tag_name = get_open_tag_name(result.tag);
137
+
138
+ if (!hb_string_is_empty(tag_name) && has_matching_close_tag_in_statements(statements, first_tag_index, tag_name)) {
139
+ result.tag = NULL;
140
+ }
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ static hb_string_T check_erb_if_conditional_open_tag(AST_ERB_IF_NODE_T* if_node) {
147
+ if (!if_node) { return HB_STRING_NULL; }
148
+
149
+ if (!if_node->subsequent) { return HB_STRING_NULL; }
150
+
151
+ single_open_tag_result_T if_result = get_single_open_tag_from_statements(if_node->statements);
152
+ if (!if_result.tag) { return HB_STRING_NULL; }
153
+
154
+ hb_string_T common_tag_name = get_open_tag_name(if_result.tag);
155
+ if (hb_string_is_null(common_tag_name)) { return HB_STRING_NULL; }
156
+
157
+ AST_NODE_T* current = if_node->subsequent;
158
+ bool ends_with_else = false;
159
+
160
+ while (current) {
161
+ hb_array_T* branch_statements = NULL;
162
+ AST_NODE_T* next_subsequent = NULL;
163
+
164
+ if (current->type == AST_ERB_IF_NODE) {
165
+ AST_ERB_IF_NODE_T* elsif_node = (AST_ERB_IF_NODE_T*) current;
166
+ branch_statements = elsif_node->statements;
167
+ next_subsequent = elsif_node->subsequent;
168
+ } else if (current->type == AST_ERB_ELSE_NODE) {
169
+ AST_ERB_ELSE_NODE_T* else_node = (AST_ERB_ELSE_NODE_T*) current;
170
+ branch_statements = else_node->statements;
171
+ next_subsequent = NULL;
172
+ ends_with_else = true;
173
+ } else {
174
+ return HB_STRING_NULL;
175
+ }
176
+
177
+ single_open_tag_result_T branch_result = get_single_open_tag_from_statements(branch_statements);
178
+ if (!branch_result.tag) { return HB_STRING_NULL; }
179
+
180
+ hb_string_T branch_tag_name = get_open_tag_name(branch_result.tag);
181
+ if (hb_string_is_null(branch_tag_name)) { return HB_STRING_NULL; }
182
+
183
+ if (!hb_string_equals_case_insensitive(common_tag_name, branch_tag_name)) { return HB_STRING_NULL; }
184
+
185
+ current = next_subsequent;
186
+ }
187
+
188
+ if (!ends_with_else) { return HB_STRING_NULL; }
189
+
190
+ return common_tag_name;
191
+ }
192
+
193
+ static hb_string_T check_erb_unless_conditional_open_tag(AST_ERB_UNLESS_NODE_T* unless_node) {
194
+ if (!unless_node) { return HB_STRING_NULL; }
195
+ if (!unless_node->else_clause) { return HB_STRING_NULL; }
196
+
197
+ single_open_tag_result_T unless_result = get_single_open_tag_from_statements(unless_node->statements);
198
+ if (!unless_result.tag) { return HB_STRING_NULL; }
199
+
200
+ hb_string_T common_tag_name = get_open_tag_name(unless_result.tag);
201
+ if (hb_string_is_null(common_tag_name)) { return HB_STRING_NULL; }
202
+
203
+ single_open_tag_result_T else_result = get_single_open_tag_from_statements(unless_node->else_clause->statements);
204
+ if (!else_result.tag) { return HB_STRING_NULL; }
205
+
206
+ hb_string_T else_tag_name = get_open_tag_name(else_result.tag);
207
+ if (hb_string_is_null(else_tag_name)) { return HB_STRING_NULL; }
208
+
209
+ if (!hb_string_equals_case_insensitive(common_tag_name, else_tag_name)) { return HB_STRING_NULL; }
210
+
211
+ return common_tag_name;
212
+ }
213
+
214
+ static size_t find_matching_close_tag(
215
+ hb_array_T* siblings,
216
+ size_t start_index,
217
+ hb_string_T tag_name,
218
+ AST_HTML_CLOSE_TAG_NODE_T** out_close_tag
219
+ ) {
220
+ *out_close_tag = NULL;
221
+
222
+ for (size_t i = start_index + 1; i < hb_array_size(siblings); i++) {
223
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(siblings, i);
224
+ if (!node) { continue; }
225
+
226
+ if (node->type == AST_HTML_CLOSE_TAG_NODE) {
227
+ AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node;
228
+
229
+ if (close_tag->tag_name && !hb_string_is_empty(close_tag->tag_name->value)) {
230
+ if (hb_string_equals_case_insensitive(tag_name, close_tag->tag_name->value)) {
231
+ *out_close_tag = close_tag;
232
+ return i;
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ return (size_t) -1;
239
+ }
240
+
241
+ static token_T* get_first_branch_tag_name_token(AST_ERB_IF_NODE_T* if_node) {
242
+ single_open_tag_result_T result = get_single_open_tag_from_statements(if_node->statements);
243
+
244
+ return result.tag ? result.tag->tag_name : NULL;
245
+ }
246
+
247
+ static token_T* get_first_branch_tag_name_token_unless(AST_ERB_UNLESS_NODE_T* unless_node) {
248
+ single_open_tag_result_T result = get_single_open_tag_from_statements(unless_node->statements);
249
+
250
+ return result.tag ? result.tag->tag_name : NULL;
251
+ }
252
+
253
+ static void add_multiple_tags_error_to_erb_node(
254
+ AST_NODE_T* erb_node,
255
+ AST_HTML_OPEN_TAG_NODE_T* second_tag,
256
+ hb_allocator_T* allocator
257
+ ) {
258
+ if (!erb_node || !second_tag) { return; }
259
+
260
+ CONDITIONAL_ELEMENT_MULTIPLE_TAGS_ERROR_T* error = conditional_element_multiple_tags_error_init(
261
+ second_tag->base.location.start.line,
262
+ second_tag->base.location.start.column,
263
+ erb_node->location.start,
264
+ erb_node->location.end,
265
+ allocator
266
+ );
267
+
268
+ if (!erb_node->errors) { erb_node->errors = hb_array_init(0, allocator); }
269
+
270
+ hb_array_append(erb_node->errors, error);
271
+ }
272
+
273
+ static void check_and_report_multiple_tags_in_if(AST_ERB_IF_NODE_T* if_node, hb_allocator_T* allocator) {
274
+ if (!if_node || !if_node->subsequent) { return; }
275
+
276
+ single_open_tag_result_T if_result = get_single_open_tag_from_statements(if_node->statements);
277
+
278
+ if (if_result.has_multiple_tags) {
279
+ add_multiple_tags_error_to_erb_node((AST_NODE_T*) if_node, if_result.second_tag, allocator);
280
+ return;
281
+ }
282
+
283
+ if (!if_result.tag) { return; }
284
+
285
+ AST_NODE_T* current = if_node->subsequent;
286
+ bool ends_with_else = false;
287
+
288
+ while (current) {
289
+ hb_array_T* branch_statements = NULL;
290
+ AST_NODE_T* next_subsequent = NULL;
291
+
292
+ if (current->type == AST_ERB_IF_NODE) {
293
+ AST_ERB_IF_NODE_T* elsif_node = (AST_ERB_IF_NODE_T*) current;
294
+ branch_statements = elsif_node->statements;
295
+ next_subsequent = elsif_node->subsequent;
296
+ } else if (current->type == AST_ERB_ELSE_NODE) {
297
+ AST_ERB_ELSE_NODE_T* else_node = (AST_ERB_ELSE_NODE_T*) current;
298
+ branch_statements = else_node->statements;
299
+ next_subsequent = NULL;
300
+ ends_with_else = true;
301
+ } else {
302
+ return;
303
+ }
304
+
305
+ single_open_tag_result_T branch_result = get_single_open_tag_from_statements(branch_statements);
306
+ if (branch_result.has_multiple_tags) {
307
+ add_multiple_tags_error_to_erb_node(current, branch_result.second_tag, allocator);
308
+ return;
309
+ }
310
+ if (!branch_result.tag) { return; }
311
+
312
+ current = next_subsequent;
313
+ }
314
+
315
+ (void) ends_with_else;
316
+ }
317
+
318
+ static void check_and_report_multiple_tags_in_unless(AST_ERB_UNLESS_NODE_T* unless_node, hb_allocator_T* allocator) {
319
+ if (!unless_node || !unless_node->else_clause) { return; }
320
+
321
+ single_open_tag_result_T unless_result = get_single_open_tag_from_statements(unless_node->statements);
322
+
323
+ if (unless_result.has_multiple_tags) {
324
+ add_multiple_tags_error_to_erb_node((AST_NODE_T*) unless_node, unless_result.second_tag, allocator);
325
+ return;
326
+ }
327
+
328
+ if (!unless_result.tag) { return; }
329
+
330
+ single_open_tag_result_T else_result = get_single_open_tag_from_statements(unless_node->else_clause->statements);
331
+
332
+ if (else_result.has_multiple_tags) {
333
+ add_multiple_tags_error_to_erb_node((AST_NODE_T*) unless_node->else_clause, else_result.second_tag, allocator);
334
+ return;
335
+ }
336
+ }
337
+
338
+ static void rewrite_conditional_open_tags(hb_array_T* nodes, hb_array_T* document_errors, hb_allocator_T* allocator) {
339
+ (void) document_errors;
340
+
341
+ if (!nodes || hb_array_size(nodes) == 0) { return; }
342
+
343
+ hb_array_T* consumed_indices = hb_array_init(8, allocator);
344
+
345
+ for (size_t i = 0; i < hb_array_size(nodes); i++) {
346
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
347
+ if (!node) { continue; }
348
+
349
+ hb_string_T tag_name = HB_STRING_NULL;
350
+ AST_NODE_T* conditional_node = NULL;
351
+ token_T* tag_name_token = NULL;
352
+
353
+ if (node->type == AST_ERB_IF_NODE) {
354
+ AST_ERB_IF_NODE_T* if_node = (AST_ERB_IF_NODE_T*) node;
355
+ tag_name = check_erb_if_conditional_open_tag(if_node);
356
+
357
+ if (!hb_string_is_null(tag_name)) {
358
+ conditional_node = node;
359
+ tag_name_token = get_first_branch_tag_name_token(if_node);
360
+ } else {
361
+ check_and_report_multiple_tags_in_if(if_node, allocator);
362
+ }
363
+ } else if (node->type == AST_ERB_UNLESS_NODE) {
364
+ AST_ERB_UNLESS_NODE_T* unless_node = (AST_ERB_UNLESS_NODE_T*) node;
365
+ tag_name = check_erb_unless_conditional_open_tag(unless_node);
366
+
367
+ if (!hb_string_is_null(tag_name)) {
368
+ conditional_node = node;
369
+ tag_name_token = get_first_branch_tag_name_token_unless(unless_node);
370
+ } else {
371
+ check_and_report_multiple_tags_in_unless(unless_node, allocator);
372
+ }
373
+ }
374
+
375
+ if (hb_string_is_null(tag_name) || !conditional_node || !tag_name_token) { continue; }
376
+
377
+ AST_HTML_CLOSE_TAG_NODE_T* close_tag = NULL;
378
+ size_t close_index = find_matching_close_tag(nodes, i, tag_name, &close_tag);
379
+
380
+ if (close_index == (size_t) -1 || !close_tag) { continue; }
381
+
382
+ hb_array_T* body = hb_array_init(8, allocator);
383
+
384
+ for (size_t j = i + 1; j < close_index; j++) {
385
+ AST_NODE_T* body_node = (AST_NODE_T*) hb_array_get(nodes, j);
386
+ if (body_node) { hb_array_append(body, body_node); }
387
+ }
388
+
389
+ position_T start_position = conditional_node->location.start;
390
+ position_T end_position = close_tag->base.location.end;
391
+
392
+ hb_array_T* conditional_open_tag_errors = hb_array_init(0, allocator);
393
+
394
+ AST_HTML_CONDITIONAL_OPEN_TAG_NODE_T* conditional_open_tag = ast_html_conditional_open_tag_node_init(
395
+ conditional_node,
396
+ tag_name_token,
397
+ false,
398
+ conditional_node->location.start,
399
+ conditional_node->location.end,
400
+ conditional_open_tag_errors,
401
+ allocator
402
+ );
403
+
404
+ hb_array_T* element_errors = hb_array_init(0, allocator);
405
+
406
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
407
+ (AST_NODE_T*) conditional_open_tag,
408
+ tag_name_token,
409
+ body,
410
+ (AST_NODE_T*) close_tag,
411
+ false,
412
+ ELEMENT_SOURCE_HTML,
413
+ start_position,
414
+ end_position,
415
+ element_errors,
416
+ allocator
417
+ );
418
+
419
+ hb_array_set(nodes, i, element);
420
+
421
+ for (size_t j = i + 1; j <= close_index; j++) {
422
+ size_t* index = malloc(sizeof(size_t));
423
+
424
+ if (index) {
425
+ *index = j;
426
+ hb_array_append(consumed_indices, index);
427
+ }
428
+ }
429
+ }
430
+
431
+ if (hb_array_size(consumed_indices) > 0) {
432
+ hb_array_T* new_nodes = hb_array_init(hb_array_size(nodes), allocator);
433
+
434
+ for (size_t i = 0; i < hb_array_size(nodes); i++) {
435
+ bool consumed = false;
436
+
437
+ for (size_t j = 0; j < hb_array_size(consumed_indices); j++) {
438
+ size_t* stored_index = (size_t*) hb_array_get(consumed_indices, j);
439
+
440
+ if (stored_index && *stored_index == i) {
441
+ consumed = true;
442
+ break;
443
+ }
444
+ }
445
+
446
+ if (!consumed) {
447
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
448
+ if (node) { hb_array_append(new_nodes, node); }
449
+ }
450
+ }
451
+
452
+ nodes->size = 0;
453
+
454
+ for (size_t i = 0; i < hb_array_size(new_nodes); i++) {
455
+ hb_array_append(nodes, hb_array_get(new_nodes, i));
456
+ }
457
+
458
+ hb_array_free(&new_nodes);
459
+ }
460
+
461
+ for (size_t i = 0; i < hb_array_size(consumed_indices); i++) {
462
+ size_t* index = (size_t*) hb_array_get(consumed_indices, i);
463
+ if (index) { free(index); }
464
+ }
465
+
466
+ hb_array_free(&consumed_indices);
467
+ }
468
+
469
+ static void transform_conditional_open_tags_in_array(hb_array_T* array, conditional_open_tags_context_T* context) {
470
+ if (!array) { return; }
471
+
472
+ for (size_t i = 0; i < hb_array_size(array); i++) {
473
+ AST_NODE_T* child = (AST_NODE_T*) hb_array_get(array, i);
474
+ if (child) { herb_visit_node(child, transform_conditional_open_tags_visitor, context); }
475
+ }
476
+
477
+ rewrite_conditional_open_tags(array, context->document_errors, context->allocator);
478
+ }
479
+
480
+ static bool transform_conditional_open_tags_visitor(const AST_NODE_T* node, void* data) {
481
+ if (!node) { return false; }
482
+
483
+ conditional_open_tags_context_T* context = (conditional_open_tags_context_T*) data;
484
+
485
+ switch (node->type) {
486
+ case AST_DOCUMENT_NODE: {
487
+ AST_DOCUMENT_NODE_T* doc = (AST_DOCUMENT_NODE_T*) node;
488
+ transform_conditional_open_tags_in_array(doc->children, context);
489
+ return false;
490
+ }
491
+
492
+ case AST_HTML_ELEMENT_NODE: {
493
+ AST_HTML_ELEMENT_NODE_T* element = (AST_HTML_ELEMENT_NODE_T*) node;
494
+ transform_conditional_open_tags_in_array(element->body, context);
495
+ return false;
496
+ }
497
+
498
+ case AST_HTML_CONDITIONAL_ELEMENT_NODE: {
499
+ AST_HTML_CONDITIONAL_ELEMENT_NODE_T* conditional = (AST_HTML_CONDITIONAL_ELEMENT_NODE_T*) node;
500
+ transform_conditional_open_tags_in_array(conditional->body, context);
501
+ return false;
502
+ }
503
+
504
+ case AST_ERB_IF_NODE: {
505
+ AST_ERB_IF_NODE_T* if_node = (AST_ERB_IF_NODE_T*) node;
506
+ transform_conditional_open_tags_in_array(if_node->statements, context);
507
+
508
+ if (if_node->subsequent) { herb_visit_node(if_node->subsequent, transform_conditional_open_tags_visitor, data); }
509
+
510
+ return false;
511
+ }
512
+
513
+ case AST_ERB_ELSE_NODE: {
514
+ AST_ERB_ELSE_NODE_T* else_node = (AST_ERB_ELSE_NODE_T*) node;
515
+ transform_conditional_open_tags_in_array(else_node->statements, context);
516
+ return false;
517
+ }
518
+
519
+ case AST_ERB_UNLESS_NODE: {
520
+ AST_ERB_UNLESS_NODE_T* unless_node = (AST_ERB_UNLESS_NODE_T*) node;
521
+ transform_conditional_open_tags_in_array(unless_node->statements, context);
522
+
523
+ if (unless_node->else_clause) {
524
+ herb_visit_node((AST_NODE_T*) unless_node->else_clause, transform_conditional_open_tags_visitor, data);
525
+ }
526
+ return false;
527
+ }
528
+
529
+ case AST_ERB_BLOCK_NODE: {
530
+ AST_ERB_BLOCK_NODE_T* block_node = (AST_ERB_BLOCK_NODE_T*) node;
531
+ transform_conditional_open_tags_in_array(block_node->body, context);
532
+ return false;
533
+ }
534
+
535
+ case AST_ERB_WHILE_NODE: {
536
+ AST_ERB_WHILE_NODE_T* while_node = (AST_ERB_WHILE_NODE_T*) node;
537
+ transform_conditional_open_tags_in_array(while_node->statements, context);
538
+ return false;
539
+ }
540
+
541
+ case AST_ERB_UNTIL_NODE: {
542
+ AST_ERB_UNTIL_NODE_T* until_node = (AST_ERB_UNTIL_NODE_T*) node;
543
+ transform_conditional_open_tags_in_array(until_node->statements, context);
544
+ return false;
545
+ }
546
+
547
+ case AST_ERB_FOR_NODE: {
548
+ AST_ERB_FOR_NODE_T* for_node = (AST_ERB_FOR_NODE_T*) node;
549
+ transform_conditional_open_tags_in_array(for_node->statements, context);
550
+ return false;
551
+ }
552
+
553
+ case AST_ERB_CASE_NODE: {
554
+ AST_ERB_CASE_NODE_T* case_node = (AST_ERB_CASE_NODE_T*) node;
555
+ transform_conditional_open_tags_in_array(case_node->children, context);
556
+
557
+ for (size_t i = 0; i < hb_array_size(case_node->conditions); i++) {
558
+ AST_NODE_T* when_node = (AST_NODE_T*) hb_array_get(case_node->conditions, i);
559
+ if (when_node) { herb_visit_node(when_node, transform_conditional_open_tags_visitor, data); }
560
+ }
561
+
562
+ if (case_node->else_clause) {
563
+ herb_visit_node((AST_NODE_T*) case_node->else_clause, transform_conditional_open_tags_visitor, data);
564
+ }
565
+
566
+ return false;
567
+ }
568
+
569
+ case AST_ERB_CASE_MATCH_NODE: {
570
+ AST_ERB_CASE_MATCH_NODE_T* case_match_node = (AST_ERB_CASE_MATCH_NODE_T*) node;
571
+ transform_conditional_open_tags_in_array(case_match_node->children, context);
572
+
573
+ for (size_t i = 0; i < hb_array_size(case_match_node->conditions); i++) {
574
+ AST_NODE_T* in_node = (AST_NODE_T*) hb_array_get(case_match_node->conditions, i);
575
+ if (in_node) { herb_visit_node(in_node, transform_conditional_open_tags_visitor, data); }
576
+ }
577
+
578
+ if (case_match_node->else_clause) {
579
+ herb_visit_node((AST_NODE_T*) case_match_node->else_clause, transform_conditional_open_tags_visitor, data);
580
+ }
581
+
582
+ return false;
583
+ }
584
+
585
+ case AST_ERB_WHEN_NODE: {
586
+ AST_ERB_WHEN_NODE_T* when_node = (AST_ERB_WHEN_NODE_T*) node;
587
+ transform_conditional_open_tags_in_array(when_node->statements, context);
588
+ return false;
589
+ }
590
+
591
+ case AST_ERB_IN_NODE: {
592
+ AST_ERB_IN_NODE_T* in_node = (AST_ERB_IN_NODE_T*) node;
593
+ transform_conditional_open_tags_in_array(in_node->statements, context);
594
+ return false;
595
+ }
596
+
597
+ case AST_ERB_BEGIN_NODE: {
598
+ AST_ERB_BEGIN_NODE_T* begin_node = (AST_ERB_BEGIN_NODE_T*) node;
599
+ transform_conditional_open_tags_in_array(begin_node->statements, context);
600
+
601
+ if (begin_node->rescue_clause) {
602
+ herb_visit_node((AST_NODE_T*) begin_node->rescue_clause, transform_conditional_open_tags_visitor, data);
603
+ }
604
+
605
+ if (begin_node->else_clause) {
606
+ herb_visit_node((AST_NODE_T*) begin_node->else_clause, transform_conditional_open_tags_visitor, data);
607
+ }
608
+
609
+ if (begin_node->ensure_clause) {
610
+ herb_visit_node((AST_NODE_T*) begin_node->ensure_clause, transform_conditional_open_tags_visitor, data);
611
+ }
612
+
613
+ return false;
614
+ }
615
+
616
+ case AST_ERB_RESCUE_NODE: {
617
+ AST_ERB_RESCUE_NODE_T* rescue_node = (AST_ERB_RESCUE_NODE_T*) node;
618
+ transform_conditional_open_tags_in_array(rescue_node->statements, context);
619
+
620
+ if (rescue_node->subsequent) {
621
+ herb_visit_node((AST_NODE_T*) rescue_node->subsequent, transform_conditional_open_tags_visitor, data);
622
+ }
623
+
624
+ return false;
625
+ }
626
+
627
+ case AST_ERB_ENSURE_NODE: {
628
+ AST_ERB_ENSURE_NODE_T* ensure_node = (AST_ERB_ENSURE_NODE_T*) node;
629
+ transform_conditional_open_tags_in_array(ensure_node->statements, context);
630
+ return false;
631
+ }
632
+
633
+ default: return true;
634
+ }
635
+ }
636
+
637
+ void herb_transform_conditional_open_tags(AST_DOCUMENT_NODE_T* document, hb_allocator_T* allocator) {
638
+ conditional_open_tags_context_T context = { .document_errors = document->base.errors, .allocator = allocator };
639
+ herb_visit_node((AST_NODE_T*) document, transform_conditional_open_tags_visitor, &context);
640
+ }
@@ -0,0 +1,9 @@
1
+ #ifndef HERB_ANALYZE_CONDITIONAL_OPEN_TAGS_H
2
+ #define HERB_ANALYZE_CONDITIONAL_OPEN_TAGS_H
3
+
4
+ #include "../ast_nodes.h"
5
+ #include "../util/hb_allocator.h"
6
+
7
+ void herb_transform_conditional_open_tags(AST_DOCUMENT_NODE_T* document, hb_allocator_T* allocator);
8
+
9
+ #endif