@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,748 @@
1
+ #include "../../include/analyze/action_view/tag_helpers.h"
2
+ #include "../../include/analyze/action_view/attribute_extraction_helpers.h"
3
+ #include "../../include/analyze/action_view/tag_helper_handler.h"
4
+ #include "../../include/analyze/action_view/tag_helper_node_builders.h"
5
+ #include "../../include/analyze/analyze.h"
6
+ #include "../../include/ast_nodes.h"
7
+ #include "../../include/html_util.h"
8
+ #include "../../include/position.h"
9
+ #include "../../include/util/hb_allocator.h"
10
+ #include "../../include/util/hb_array.h"
11
+ #include "../../include/util/hb_string.h"
12
+ #include "../../include/visitor.h"
13
+
14
+ #include <prism.h>
15
+ #include <stdbool.h>
16
+ #include <stdlib.h>
17
+ #include <string.h>
18
+
19
+ extern bool detect_link_to(pm_call_node_t*, pm_parser_t*);
20
+ extern bool is_route_helper_node(pm_node_t*, pm_parser_t*);
21
+ extern char* wrap_in_url_for(const char*, size_t, hb_allocator_T*);
22
+ extern char* extract_link_to_href(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
23
+ extern bool detect_turbo_frame_tag(pm_call_node_t*, pm_parser_t*);
24
+ extern char* extract_turbo_frame_tag_id(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
25
+
26
+ typedef struct {
27
+ pm_parser_t parser;
28
+ pm_node_t* root;
29
+ const uint8_t* prism_source;
30
+ char* content_string;
31
+ tag_helper_info_T* info;
32
+ const tag_helper_handler_T* matched_handler;
33
+ const char* original_source;
34
+ size_t erb_content_offset;
35
+ } tag_helper_parse_context_T;
36
+
37
+ static tag_helper_parse_context_T* parse_tag_helper_content(
38
+ const char* content_string,
39
+ const char* original_source,
40
+ size_t erb_content_offset,
41
+ hb_allocator_T* allocator
42
+ ) {
43
+ if (!content_string) { return NULL; }
44
+
45
+ tag_helper_parse_context_T* parse_context = hb_allocator_alloc(allocator, sizeof(tag_helper_parse_context_T));
46
+ if (!parse_context) { return NULL; }
47
+
48
+ parse_context->content_string = hb_allocator_strdup(allocator, content_string);
49
+ parse_context->prism_source = (const uint8_t*) parse_context->content_string;
50
+ parse_context->original_source = original_source;
51
+ parse_context->erb_content_offset = erb_content_offset;
52
+
53
+ pm_parser_init(&parse_context->parser, parse_context->prism_source, strlen(parse_context->content_string), NULL);
54
+ parse_context->root = pm_parse(&parse_context->parser);
55
+
56
+ if (!parse_context->root) {
57
+ pm_parser_free(&parse_context->parser);
58
+ hb_allocator_dealloc(allocator, parse_context->content_string);
59
+ hb_allocator_dealloc(allocator, parse_context);
60
+ return NULL;
61
+ }
62
+
63
+ parse_context->info = tag_helper_info_init(allocator);
64
+ tag_helper_search_data_T search = { .tag_helper_node = NULL,
65
+ .source = parse_context->prism_source,
66
+ .parser = &parse_context->parser,
67
+ .info = parse_context->info,
68
+ .found = false };
69
+ pm_visit_node(parse_context->root, search_tag_helper_node, &search);
70
+
71
+ if (!search.found) {
72
+ tag_helper_info_free(&parse_context->info);
73
+ pm_node_destroy(&parse_context->parser, parse_context->root);
74
+ pm_parser_free(&parse_context->parser);
75
+ hb_allocator_dealloc(allocator, parse_context->content_string);
76
+ hb_allocator_dealloc(allocator, parse_context);
77
+ return NULL;
78
+ }
79
+
80
+ parse_context->matched_handler = search.matched_handler;
81
+ return parse_context;
82
+ }
83
+
84
+ static void free_tag_helper_parse_context(tag_helper_parse_context_T* parse_context) {
85
+ if (!parse_context) { return; }
86
+
87
+ hb_allocator_T* allocator = parse_context->info ? parse_context->info->allocator : NULL;
88
+
89
+ tag_helper_info_free(&parse_context->info);
90
+ pm_node_destroy(&parse_context->parser, parse_context->root);
91
+ pm_parser_free(&parse_context->parser);
92
+
93
+ if (allocator) {
94
+ hb_allocator_dealloc(allocator, parse_context->content_string);
95
+ hb_allocator_dealloc(allocator, parse_context);
96
+ }
97
+ }
98
+
99
+ bool search_tag_helper_node(const pm_node_t* node, void* data) {
100
+ tag_helper_search_data_T* search_data = (tag_helper_search_data_T*) data;
101
+
102
+ if (node->type == PM_CALL_NODE) {
103
+ pm_call_node_t* call_node = (pm_call_node_t*) node;
104
+ tag_helper_handler_T* handlers = get_tag_helper_handlers();
105
+ size_t handlers_count = get_tag_helper_handlers_count();
106
+
107
+ for (size_t i = 0; i < handlers_count; i++) {
108
+ if (handlers[i].detect(call_node, search_data->parser)) {
109
+ search_data->tag_helper_node = node;
110
+ search_data->matched_handler = &handlers[i];
111
+ search_data->found = true;
112
+
113
+ if (search_data->info) {
114
+ search_data->info->call_node = call_node;
115
+ search_data->info->tag_name =
116
+ handlers[i].extract_tag_name(call_node, search_data->parser, search_data->info->allocator);
117
+ search_data->info->content =
118
+ handlers[i].extract_content(call_node, search_data->parser, search_data->info->allocator);
119
+ search_data->info->has_block = handlers[i].supports_block();
120
+ }
121
+
122
+ return true;
123
+ }
124
+ }
125
+ }
126
+
127
+ pm_visit_child_nodes(node, search_tag_helper_node, search_data);
128
+
129
+ return search_data->found;
130
+ }
131
+
132
+ position_T byte_offset_to_position(const char* source, size_t offset) {
133
+ position_T position = { .line = 1, .column = 1 };
134
+
135
+ if (!source) { return position; }
136
+
137
+ for (size_t i = 0; i < offset && source[i] != '\0'; i++) {
138
+ if (source[i] == '\n') {
139
+ position.line++;
140
+ position.column = 1;
141
+ } else {
142
+ position.column++;
143
+ }
144
+ }
145
+
146
+ return position;
147
+ }
148
+
149
+ position_T prism_location_to_position_with_offset(
150
+ const pm_location_t* pm_location,
151
+ const char* original_source,
152
+ size_t erb_content_offset,
153
+ const uint8_t* erb_content_source
154
+ ) {
155
+ position_T default_position = { .line = 1, .column = 1 };
156
+
157
+ if (!pm_location || !pm_location->start || !original_source || !erb_content_source) { return default_position; }
158
+
159
+ size_t offset_in_erb = (size_t) (pm_location->start - erb_content_source);
160
+ size_t total_offset = erb_content_offset + offset_in_erb;
161
+
162
+ size_t source_length = strlen(original_source);
163
+ if (total_offset > source_length) { return byte_offset_to_position(original_source, erb_content_offset); }
164
+
165
+ return byte_offset_to_position(original_source, total_offset);
166
+ }
167
+
168
+ size_t calculate_byte_offset_from_position(const char* source, position_T position) {
169
+ if (!source) { return 0; }
170
+
171
+ size_t offset = 0;
172
+ uint32_t line = 1;
173
+ uint32_t column = 1;
174
+
175
+ while (source[offset] != '\0') {
176
+ if (line == position.line && column == position.column) { return offset; }
177
+
178
+ if (source[offset] == '\n') {
179
+ line++;
180
+ column = 1;
181
+ } else {
182
+ column++;
183
+ }
184
+
185
+ offset++;
186
+ }
187
+
188
+ return offset;
189
+ }
190
+
191
+ static void prism_node_location_to_positions(
192
+ const pm_location_t* location,
193
+ tag_helper_parse_context_T* parse_context,
194
+ position_T* out_start,
195
+ position_T* out_end
196
+ ) {
197
+ *out_start = prism_location_to_position_with_offset(
198
+ location,
199
+ parse_context->original_source,
200
+ parse_context->erb_content_offset,
201
+ parse_context->prism_source
202
+ );
203
+
204
+ pm_location_t end_location = { .start = location->end, .end = location->end };
205
+ *out_end = prism_location_to_position_with_offset(
206
+ &end_location,
207
+ parse_context->original_source,
208
+ parse_context->erb_content_offset,
209
+ parse_context->prism_source
210
+ );
211
+ }
212
+
213
+ static void calculate_tag_name_positions(
214
+ tag_helper_parse_context_T* parse_context,
215
+ position_T default_start,
216
+ position_T default_end,
217
+ position_T* out_start,
218
+ position_T* out_end
219
+ ) {
220
+ *out_start = default_start;
221
+ *out_end = default_end;
222
+
223
+ if (parse_context->info->call_node && parse_context->info->call_node->message_loc.start) {
224
+ prism_node_location_to_positions(&parse_context->info->call_node->message_loc, parse_context, out_start, out_end);
225
+ }
226
+ }
227
+
228
+ static AST_NODE_T* transform_tag_helper_with_attributes(
229
+ AST_ERB_CONTENT_NODE_T* erb_node,
230
+ analyze_ruby_context_T* context,
231
+ tag_helper_parse_context_T* parse_context
232
+ ) {
233
+ if (!erb_node || !context || !parse_context) { return NULL; }
234
+ hb_allocator_T* allocator = context->allocator;
235
+ const tag_helper_handler_T* handler = parse_context->matched_handler;
236
+
237
+ char* tag_name = parse_context->info->tag_name ? hb_allocator_strdup(allocator, parse_context->info->tag_name) : NULL;
238
+
239
+ position_T tag_name_start, tag_name_end;
240
+ calculate_tag_name_positions(
241
+ parse_context,
242
+ erb_node->base.location.start,
243
+ erb_node->base.location.end,
244
+ &tag_name_start,
245
+ &tag_name_end
246
+ );
247
+
248
+ hb_array_T* attributes = NULL;
249
+ if (parse_context->info->call_node) {
250
+ attributes = extract_html_attributes_from_call_node(
251
+ parse_context->info->call_node,
252
+ parse_context->prism_source,
253
+ parse_context->original_source,
254
+ parse_context->erb_content_offset,
255
+ allocator
256
+ );
257
+ }
258
+
259
+ char* helper_content = NULL;
260
+ bool content_is_ruby_expression = false;
261
+
262
+ if (parse_context->info->call_node && handler->extract_content) {
263
+ helper_content = handler->extract_content(parse_context->info->call_node, &parse_context->parser, allocator);
264
+
265
+ if (helper_content && parse_context->info->call_node->arguments) {
266
+ if (strcmp(handler->name, "content_tag") == 0 && parse_context->info->call_node->arguments->arguments.size >= 2) {
267
+ content_is_ruby_expression =
268
+ (parse_context->info->call_node->arguments->arguments.nodes[1]->type != PM_STRING_NODE);
269
+ } else if (parse_context->info->call_node->arguments->arguments.size >= 1) {
270
+ content_is_ruby_expression =
271
+ (parse_context->info->call_node->arguments->arguments.nodes[0]->type != PM_STRING_NODE);
272
+ }
273
+ }
274
+ }
275
+
276
+ if (detect_turbo_frame_tag(parse_context->info->call_node, &parse_context->parser)) {
277
+ char* id_value = extract_turbo_frame_tag_id(parse_context->info->call_node, &parse_context->parser, allocator);
278
+
279
+ if (id_value) {
280
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
281
+
282
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
283
+ position_T id_start, id_end;
284
+ prism_node_location_to_positions(&first_argument->location, parse_context, &id_start, &id_end);
285
+ bool id_is_ruby_expression = (first_argument->type != PM_STRING_NODE && first_argument->type != PM_SYMBOL_NODE);
286
+
287
+ AST_HTML_ATTRIBUTE_NODE_T* id_attribute =
288
+ id_is_ruby_expression ? create_html_attribute_with_ruby_literal("id", id_value, id_start, id_end, allocator)
289
+ : create_html_attribute_node("id", id_value, id_start, id_end, allocator);
290
+
291
+ if (id_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) id_attribute, allocator); }
292
+
293
+ hb_allocator_dealloc(allocator, id_value);
294
+ }
295
+ }
296
+
297
+ token_T* tag_name_token =
298
+ tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
299
+
300
+ hb_array_T* open_tag_children = attributes ? attributes : hb_array_init(0, allocator);
301
+
302
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
303
+ erb_node->tag_opening,
304
+ erb_node->content,
305
+ erb_node->tag_closing,
306
+ tag_name_token,
307
+ open_tag_children,
308
+ erb_node->base.location.start,
309
+ erb_node->base.location.end,
310
+ hb_array_init(0, allocator),
311
+ allocator
312
+ );
313
+
314
+ hb_array_T* body = hb_array_init(1, allocator);
315
+ bool is_void = tag_name && (strcmp(handler->name, "tag") == 0) && is_void_element(hb_string_from_c_string(tag_name));
316
+
317
+ if (helper_content) {
318
+ append_body_content_node(
319
+ body,
320
+ helper_content,
321
+ content_is_ruby_expression,
322
+ erb_node->base.location.start,
323
+ erb_node->base.location.end,
324
+ allocator
325
+ );
326
+ hb_allocator_dealloc(allocator, helper_content);
327
+ }
328
+
329
+ AST_NODE_T* close_tag = NULL;
330
+
331
+ if (!is_void) {
332
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
333
+ tag_name_token,
334
+ erb_node->base.location.end,
335
+ erb_node->base.location.end,
336
+ hb_array_init(0, allocator),
337
+ allocator
338
+ );
339
+ close_tag = (AST_NODE_T*) virtual_close;
340
+ }
341
+
342
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
343
+ (AST_NODE_T*) open_tag_node,
344
+ tag_name_token,
345
+ body,
346
+ close_tag,
347
+ is_void,
348
+ handler->source,
349
+ erb_node->base.location.start,
350
+ erb_node->base.location.end,
351
+ hb_array_init(0, allocator),
352
+ allocator
353
+ );
354
+
355
+ hb_allocator_dealloc(allocator, tag_name);
356
+ return (AST_NODE_T*) element;
357
+ }
358
+
359
+ static AST_NODE_T* transform_erb_block_to_tag_helper(
360
+ AST_ERB_BLOCK_NODE_T* block_node,
361
+ analyze_ruby_context_T* context,
362
+ tag_helper_parse_context_T* parse_context
363
+ ) {
364
+ if (!block_node || !context || !parse_context) { return NULL; }
365
+ hb_allocator_T* allocator = context->allocator;
366
+
367
+ char* tag_name = parse_context->info->tag_name ? hb_allocator_strdup(allocator, parse_context->info->tag_name) : NULL;
368
+
369
+ position_T tag_name_start, tag_name_end;
370
+ calculate_tag_name_positions(
371
+ parse_context,
372
+ block_node->base.location.start,
373
+ block_node->base.location.end,
374
+ &tag_name_start,
375
+ &tag_name_end
376
+ );
377
+
378
+ hb_array_T* attributes = NULL;
379
+ if (parse_context->info->call_node) {
380
+ attributes = extract_html_attributes_from_call_node(
381
+ parse_context->info->call_node,
382
+ parse_context->prism_source,
383
+ parse_context->original_source,
384
+ parse_context->erb_content_offset,
385
+ allocator
386
+ );
387
+ }
388
+
389
+ if (detect_link_to(parse_context->info->call_node, &parse_context->parser)
390
+ && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
391
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
392
+ size_t source_length = first_argument->location.end - first_argument->location.start;
393
+ char* href = NULL;
394
+
395
+ if (first_argument->type != PM_STRING_NODE && !is_route_helper_node(first_argument, &parse_context->parser)) {
396
+ href = wrap_in_url_for((const char*) first_argument->location.start, source_length, allocator);
397
+ } else {
398
+ href = hb_allocator_strndup(allocator, (const char*) first_argument->location.start, source_length);
399
+ }
400
+
401
+ if (href) {
402
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
403
+
404
+ position_T href_start, href_end;
405
+ prism_node_location_to_positions(&first_argument->location, parse_context, &href_start, &href_end);
406
+ bool href_is_ruby_expression = (first_argument->type != PM_STRING_NODE);
407
+
408
+ if (first_argument->type == PM_STRING_NODE) {
409
+ hb_allocator_dealloc(allocator, href);
410
+ pm_string_node_t* string_node = (pm_string_node_t*) first_argument;
411
+ size_t length = pm_string_length(&string_node->unescaped);
412
+ href = hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
413
+ }
414
+
415
+ AST_HTML_ATTRIBUTE_NODE_T* href_attribute =
416
+ create_href_attribute(href, href_is_ruby_expression, href_start, href_end, allocator);
417
+
418
+ if (href_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) href_attribute, allocator); }
419
+
420
+ hb_allocator_dealloc(allocator, href);
421
+ }
422
+ }
423
+
424
+ if (detect_turbo_frame_tag(parse_context->info->call_node, &parse_context->parser)) {
425
+ char* id_value = extract_turbo_frame_tag_id(parse_context->info->call_node, &parse_context->parser, allocator);
426
+
427
+ if (id_value) {
428
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
429
+
430
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
431
+ position_T id_start, id_end;
432
+ prism_node_location_to_positions(&first_argument->location, parse_context, &id_start, &id_end);
433
+ bool id_is_ruby_expression = (first_argument->type != PM_STRING_NODE && first_argument->type != PM_SYMBOL_NODE);
434
+
435
+ AST_HTML_ATTRIBUTE_NODE_T* id_attribute =
436
+ id_is_ruby_expression ? create_html_attribute_with_ruby_literal("id", id_value, id_start, id_end, allocator)
437
+ : create_html_attribute_node("id", id_value, id_start, id_end, allocator);
438
+
439
+ if (id_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) id_attribute, allocator); }
440
+
441
+ hb_allocator_dealloc(allocator, id_value);
442
+ }
443
+ }
444
+
445
+ token_T* tag_name_token =
446
+ tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
447
+
448
+ hb_array_T* open_tag_children = attributes ? attributes : hb_array_init(0, allocator);
449
+
450
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
451
+ block_node->tag_opening,
452
+ block_node->content,
453
+ block_node->tag_closing,
454
+ tag_name_token,
455
+ open_tag_children,
456
+ block_node->tag_opening->location.start,
457
+ block_node->tag_closing->location.end,
458
+ hb_array_init(0, allocator),
459
+ allocator
460
+ );
461
+
462
+ hb_array_T* body = block_node->body ? block_node->body : hb_array_init(0, allocator);
463
+ AST_NODE_T* close_tag = (AST_NODE_T*) block_node->end_node;
464
+
465
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
466
+ (AST_NODE_T*) open_tag_node,
467
+ tag_name_token,
468
+ body,
469
+ close_tag,
470
+ false,
471
+ parse_context->matched_handler->source,
472
+ block_node->base.location.start,
473
+ block_node->base.location.end,
474
+ hb_array_init(0, allocator),
475
+ allocator
476
+ );
477
+
478
+ hb_allocator_dealloc(allocator, tag_name);
479
+ return (AST_NODE_T*) element;
480
+ }
481
+
482
+ static AST_NODE_T* transform_link_to_helper(
483
+ AST_ERB_CONTENT_NODE_T* erb_node,
484
+ analyze_ruby_context_T* context,
485
+ tag_helper_parse_context_T* parse_context
486
+ ) {
487
+ if (!erb_node || !context || !parse_context) { return NULL; }
488
+ hb_allocator_T* allocator = context->allocator;
489
+ tag_helper_info_T* info = parse_context->info;
490
+
491
+ char* href = extract_link_to_href(info->call_node, &parse_context->parser, allocator);
492
+
493
+ hb_array_T* attributes = NULL;
494
+ pm_arguments_node_t* link_arguments = info->call_node->arguments;
495
+ bool keyword_hash_is_url = link_arguments && link_arguments->arguments.size == 2
496
+ && link_arguments->arguments.nodes[1]->type == PM_KEYWORD_HASH_NODE;
497
+
498
+ if (!keyword_hash_is_url) {
499
+ attributes = extract_html_attributes_from_call_node(
500
+ info->call_node,
501
+ parse_context->prism_source,
502
+ parse_context->original_source,
503
+ parse_context->erb_content_offset,
504
+ allocator
505
+ );
506
+ }
507
+
508
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
509
+
510
+ // `method:` implies `rel="nofollow"`
511
+ bool has_data_method = false;
512
+ hb_string_T data_method_string = hb_string("data-method");
513
+
514
+ for (size_t i = 0; i < hb_array_size(attributes); i++) {
515
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(attributes, i);
516
+
517
+ if (node->type != AST_HTML_ATTRIBUTE_NODE) { continue; }
518
+
519
+ AST_HTML_ATTRIBUTE_NODE_T* attribute = (AST_HTML_ATTRIBUTE_NODE_T*) node;
520
+
521
+ if (!attribute->name || !attribute->name->children || !hb_array_size(attribute->name->children)) { continue; }
522
+
523
+ AST_LITERAL_NODE_T* literal = (AST_LITERAL_NODE_T*) hb_array_get(attribute->name->children, 0);
524
+
525
+ if (hb_string_equals(literal->content, data_method_string)) {
526
+ has_data_method = true;
527
+ break;
528
+ }
529
+ }
530
+
531
+ if (has_data_method) {
532
+ position_T rel_position = erb_node->base.location.start;
533
+
534
+ AST_HTML_ATTRIBUTE_NODE_T* rel_attribute =
535
+ create_html_attribute_node("rel", "nofollow", rel_position, rel_position, allocator);
536
+
537
+ if (rel_attribute) { hb_array_append(attributes, rel_attribute); }
538
+ }
539
+
540
+ char* href_for_body = NULL;
541
+ bool href_for_body_is_ruby_expression = false;
542
+
543
+ if (href) {
544
+ position_T href_start = erb_node->content->location.start;
545
+ position_T href_end = href_start;
546
+ bool href_is_ruby_expression = true;
547
+
548
+ if (info->call_node && info->call_node->arguments) {
549
+ pm_arguments_node_t* arguments = info->call_node->arguments;
550
+ pm_node_t* href_argument = NULL;
551
+
552
+ if (arguments->arguments.size >= 2) {
553
+ href_argument = arguments->arguments.nodes[1];
554
+ href_is_ruby_expression = (href_argument->type != PM_STRING_NODE);
555
+ } else if (arguments->arguments.size == 1) {
556
+ href_argument = arguments->arguments.nodes[0];
557
+ href_is_ruby_expression = true;
558
+ }
559
+
560
+ if (href_argument) {
561
+ prism_node_location_to_positions(&href_argument->location, parse_context, &href_start, &href_end);
562
+ }
563
+ }
564
+
565
+ AST_HTML_ATTRIBUTE_NODE_T* href_attribute =
566
+ create_href_attribute(href, href_is_ruby_expression, href_start, href_end, allocator);
567
+
568
+ if (href_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) href_attribute, allocator); }
569
+ if (!info->content) {
570
+ href_for_body = hb_allocator_strdup(allocator, href);
571
+ href_for_body_is_ruby_expression = href_is_ruby_expression;
572
+ }
573
+
574
+ hb_allocator_dealloc(allocator, href);
575
+ }
576
+
577
+ position_T tag_name_start, tag_name_end;
578
+ calculate_tag_name_positions(
579
+ parse_context,
580
+ erb_node->base.location.start,
581
+ erb_node->base.location.end,
582
+ &tag_name_start,
583
+ &tag_name_end
584
+ );
585
+
586
+ token_T* tag_name_token = create_synthetic_token(allocator, "a", TOKEN_IDENTIFIER, tag_name_start, tag_name_end);
587
+
588
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
589
+ erb_node->tag_opening,
590
+ erb_node->content,
591
+ erb_node->tag_closing,
592
+ tag_name_token,
593
+ attributes,
594
+ erb_node->base.location.start,
595
+ erb_node->base.location.end,
596
+ hb_array_init(0, allocator),
597
+ allocator
598
+ );
599
+
600
+ hb_array_T* body = hb_array_init(1, allocator);
601
+
602
+ if (info->content) {
603
+ bool content_is_ruby_expression = false;
604
+
605
+ if (info->call_node && info->call_node->arguments && info->call_node->arguments->arguments.size >= 1) {
606
+ pm_node_t* first_argument = info->call_node->arguments->arguments.nodes[0];
607
+ content_is_ruby_expression = (first_argument->type != PM_STRING_NODE);
608
+ }
609
+
610
+ append_body_content_node(
611
+ body,
612
+ info->content,
613
+ content_is_ruby_expression,
614
+ erb_node->base.location.start,
615
+ erb_node->base.location.end,
616
+ allocator
617
+ );
618
+ } else if (href_for_body) {
619
+ append_body_content_node(
620
+ body,
621
+ href_for_body,
622
+ href_for_body_is_ruby_expression,
623
+ erb_node->base.location.start,
624
+ erb_node->base.location.end,
625
+ allocator
626
+ );
627
+
628
+ hb_allocator_dealloc(allocator, href_for_body);
629
+ }
630
+
631
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
632
+ tag_name_token,
633
+ erb_node->base.location.end,
634
+ erb_node->base.location.end,
635
+ hb_array_init(0, allocator),
636
+ allocator
637
+ );
638
+
639
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
640
+ (AST_NODE_T*) open_tag_node,
641
+ tag_name_token,
642
+ body,
643
+ (AST_NODE_T*) virtual_close,
644
+ false,
645
+ parse_context->matched_handler->source,
646
+ erb_node->base.location.start,
647
+ erb_node->base.location.end,
648
+ hb_array_init(0, allocator),
649
+ allocator
650
+ );
651
+
652
+ return (AST_NODE_T*) element;
653
+ }
654
+
655
+ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
656
+ if (!node || !context) { return; }
657
+
658
+ hb_array_T* array = NULL;
659
+
660
+ switch (node->type) {
661
+ case AST_DOCUMENT_NODE: array = ((AST_DOCUMENT_NODE_T*) node)->children; break;
662
+ case AST_HTML_ELEMENT_NODE: array = ((AST_HTML_ELEMENT_NODE_T*) node)->body; break;
663
+ case AST_HTML_OPEN_TAG_NODE: array = ((AST_HTML_OPEN_TAG_NODE_T*) node)->children; break;
664
+ case AST_HTML_ATTRIBUTE_VALUE_NODE: array = ((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children; break;
665
+ case AST_ERB_BLOCK_NODE: array = ((AST_ERB_BLOCK_NODE_T*) node)->body; break;
666
+ default: return;
667
+ }
668
+
669
+ if (!array) { return; }
670
+
671
+ for (size_t i = 0; i < hb_array_size(array); i++) {
672
+ AST_NODE_T* child = hb_array_get(array, i);
673
+ if (!child) { continue; }
674
+
675
+ AST_NODE_T* replacement = NULL;
676
+
677
+ if (child->type == AST_ERB_BLOCK_NODE) {
678
+ AST_ERB_BLOCK_NODE_T* block_node = (AST_ERB_BLOCK_NODE_T*) child;
679
+ token_T* block_content = block_node->content;
680
+
681
+ if (block_content && !hb_string_is_empty(block_content->value)) {
682
+ char* block_string = hb_string_to_c_string_using_malloc(block_content->value);
683
+ size_t erb_content_offset = 0;
684
+
685
+ if (context->source) {
686
+ erb_content_offset = calculate_byte_offset_from_position(context->source, block_content->location.start);
687
+ }
688
+
689
+ tag_helper_parse_context_T* parse_context =
690
+ parse_tag_helper_content(block_string, context->source, erb_content_offset, context->allocator);
691
+
692
+ if (parse_context) {
693
+ replacement = transform_erb_block_to_tag_helper(block_node, context, parse_context);
694
+ free_tag_helper_parse_context(parse_context);
695
+ }
696
+
697
+ free(block_string);
698
+ }
699
+ } else if (child->type == AST_ERB_CONTENT_NODE) {
700
+ AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) child;
701
+ token_T* tag_opening = erb_node->tag_opening;
702
+
703
+ if (tag_opening && !hb_string_is_empty(tag_opening->value)) {
704
+ const char* opening_string = tag_opening->value.data;
705
+
706
+ if (opening_string && strstr(opening_string, "#") != NULL) { continue; }
707
+ }
708
+
709
+ token_T* erb_content = erb_node->content;
710
+
711
+ if (erb_content && !hb_string_is_empty(erb_content->value)) {
712
+ char* erb_string = hb_string_to_c_string_using_malloc(erb_content->value);
713
+ size_t erb_content_offset = 0;
714
+
715
+ if (context->source) {
716
+ erb_content_offset = calculate_byte_offset_from_position(context->source, erb_content->location.start);
717
+ }
718
+
719
+ tag_helper_parse_context_T* parse_context =
720
+ parse_tag_helper_content(erb_string, context->source, erb_content_offset, context->allocator);
721
+
722
+ if (parse_context) {
723
+ if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
724
+ replacement = transform_link_to_helper(erb_node, context, parse_context);
725
+ } else {
726
+ replacement = transform_tag_helper_with_attributes(erb_node, context, parse_context);
727
+ }
728
+
729
+ free_tag_helper_parse_context(parse_context);
730
+ }
731
+
732
+ free(erb_string);
733
+ }
734
+ }
735
+
736
+ if (replacement) { hb_array_set(array, i, replacement); }
737
+ }
738
+ }
739
+
740
+ bool transform_tag_helper_nodes(const AST_NODE_T* node, void* data) {
741
+ analyze_ruby_context_T* context = (analyze_ruby_context_T*) data;
742
+
743
+ transform_tag_helper_blocks(node, context);
744
+
745
+ herb_visit_child_nodes(node, transform_tag_helper_nodes, data);
746
+
747
+ return false;
748
+ }