@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,815 @@
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) {
266
+ pm_call_node_t* call = parse_context->info->call_node;
267
+
268
+ if (call->arguments) {
269
+ if (strcmp(handler->name, "content_tag") == 0 && call->arguments->arguments.size >= 2
270
+ && call->arguments->arguments.nodes[1]->type != PM_KEYWORD_HASH_NODE) {
271
+ content_is_ruby_expression = (call->arguments->arguments.nodes[1]->type != PM_STRING_NODE);
272
+ } else if (strcmp(handler->name, "content_tag") != 0 && call->arguments->arguments.size >= 1
273
+ && call->arguments->arguments.nodes[0]->type != PM_KEYWORD_HASH_NODE) {
274
+ content_is_ruby_expression = (call->arguments->arguments.nodes[0]->type != PM_STRING_NODE);
275
+ }
276
+ }
277
+
278
+ if (!content_is_ruby_expression && call->block && call->block->type == PM_BLOCK_NODE) {
279
+ pm_block_node_t* block_node = (pm_block_node_t*) call->block;
280
+
281
+ if (block_node->body && block_node->body->type == PM_STATEMENTS_NODE) {
282
+ pm_statements_node_t* statements = (pm_statements_node_t*) block_node->body;
283
+
284
+ if (statements->body.size == 1) {
285
+ content_is_ruby_expression = (statements->body.nodes[0]->type != PM_STRING_NODE);
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ if (detect_turbo_frame_tag(parse_context->info->call_node, &parse_context->parser)) {
293
+ char* id_value = extract_turbo_frame_tag_id(parse_context->info->call_node, &parse_context->parser, allocator);
294
+
295
+ if (id_value) {
296
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
297
+
298
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
299
+ position_T id_start, id_end;
300
+ prism_node_location_to_positions(&first_argument->location, parse_context, &id_start, &id_end);
301
+ bool id_is_ruby_expression = (first_argument->type != PM_STRING_NODE && first_argument->type != PM_SYMBOL_NODE);
302
+
303
+ AST_HTML_ATTRIBUTE_NODE_T* id_attribute =
304
+ id_is_ruby_expression ? create_html_attribute_with_ruby_literal("id", id_value, id_start, id_end, allocator)
305
+ : create_html_attribute_node("id", id_value, id_start, id_end, allocator);
306
+
307
+ if (id_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) id_attribute, allocator); }
308
+
309
+ hb_allocator_dealloc(allocator, id_value);
310
+ }
311
+ }
312
+
313
+ token_T* tag_name_token =
314
+ tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
315
+
316
+ hb_array_T* open_tag_children = attributes ? attributes : hb_array_init(0, allocator);
317
+
318
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
319
+ erb_node->tag_opening,
320
+ erb_node->content,
321
+ erb_node->tag_closing,
322
+ tag_name_token,
323
+ open_tag_children,
324
+ erb_node->base.location.start,
325
+ erb_node->base.location.end,
326
+ hb_array_init(0, allocator),
327
+ allocator
328
+ );
329
+
330
+ hb_array_T* body = hb_array_init(1, allocator);
331
+ bool is_void = tag_name && (strcmp(handler->name, "tag") == 0) && is_void_element(hb_string_from_c_string(tag_name));
332
+
333
+ if (helper_content) {
334
+ append_body_content_node(
335
+ body,
336
+ helper_content,
337
+ content_is_ruby_expression,
338
+ erb_node->base.location.start,
339
+ erb_node->base.location.end,
340
+ allocator
341
+ );
342
+ hb_allocator_dealloc(allocator, helper_content);
343
+ }
344
+
345
+ AST_NODE_T* close_tag = NULL;
346
+
347
+ if (!is_void) {
348
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
349
+ tag_name_token,
350
+ erb_node->base.location.end,
351
+ erb_node->base.location.end,
352
+ hb_array_init(0, allocator),
353
+ allocator
354
+ );
355
+ close_tag = (AST_NODE_T*) virtual_close;
356
+ }
357
+
358
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
359
+ (AST_NODE_T*) open_tag_node,
360
+ tag_name_token,
361
+ body,
362
+ close_tag,
363
+ is_void,
364
+ handler->source,
365
+ erb_node->base.location.start,
366
+ erb_node->base.location.end,
367
+ hb_array_init(0, allocator),
368
+ allocator
369
+ );
370
+
371
+ hb_allocator_dealloc(allocator, tag_name);
372
+ return (AST_NODE_T*) element;
373
+ }
374
+
375
+ static AST_NODE_T* transform_erb_block_to_tag_helper(
376
+ AST_ERB_BLOCK_NODE_T* block_node,
377
+ analyze_ruby_context_T* context,
378
+ tag_helper_parse_context_T* parse_context
379
+ ) {
380
+ if (!block_node || !context || !parse_context) { return NULL; }
381
+ hb_allocator_T* allocator = context->allocator;
382
+
383
+ char* tag_name = parse_context->info->tag_name ? hb_allocator_strdup(allocator, parse_context->info->tag_name) : NULL;
384
+
385
+ position_T tag_name_start, tag_name_end;
386
+ calculate_tag_name_positions(
387
+ parse_context,
388
+ block_node->base.location.start,
389
+ block_node->base.location.end,
390
+ &tag_name_start,
391
+ &tag_name_end
392
+ );
393
+
394
+ hb_array_T* attributes = NULL;
395
+ if (parse_context->info->call_node) {
396
+ attributes = extract_html_attributes_from_call_node(
397
+ parse_context->info->call_node,
398
+ parse_context->prism_source,
399
+ parse_context->original_source,
400
+ parse_context->erb_content_offset,
401
+ allocator
402
+ );
403
+ }
404
+
405
+ if (detect_link_to(parse_context->info->call_node, &parse_context->parser)
406
+ && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
407
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
408
+ size_t source_length = first_argument->location.end - first_argument->location.start;
409
+ char* href = NULL;
410
+
411
+ if (first_argument->type != PM_STRING_NODE && !is_route_helper_node(first_argument, &parse_context->parser)) {
412
+ href = wrap_in_url_for((const char*) first_argument->location.start, source_length, allocator);
413
+ } else {
414
+ href = hb_allocator_strndup(allocator, (const char*) first_argument->location.start, source_length);
415
+ }
416
+
417
+ if (href) {
418
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
419
+
420
+ position_T href_start, href_end;
421
+ prism_node_location_to_positions(&first_argument->location, parse_context, &href_start, &href_end);
422
+ bool href_is_ruby_expression = (first_argument->type != PM_STRING_NODE);
423
+
424
+ if (first_argument->type == PM_STRING_NODE) {
425
+ hb_allocator_dealloc(allocator, href);
426
+ pm_string_node_t* string_node = (pm_string_node_t*) first_argument;
427
+ size_t length = pm_string_length(&string_node->unescaped);
428
+ href = hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
429
+ }
430
+
431
+ AST_HTML_ATTRIBUTE_NODE_T* href_attribute =
432
+ create_href_attribute(href, href_is_ruby_expression, href_start, href_end, allocator);
433
+
434
+ if (href_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) href_attribute, allocator); }
435
+
436
+ hb_allocator_dealloc(allocator, href);
437
+ }
438
+ }
439
+
440
+ if (detect_turbo_frame_tag(parse_context->info->call_node, &parse_context->parser)) {
441
+ char* id_value = extract_turbo_frame_tag_id(parse_context->info->call_node, &parse_context->parser, allocator);
442
+
443
+ if (id_value) {
444
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
445
+
446
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
447
+ position_T id_start, id_end;
448
+ prism_node_location_to_positions(&first_argument->location, parse_context, &id_start, &id_end);
449
+ bool id_is_ruby_expression = (first_argument->type != PM_STRING_NODE && first_argument->type != PM_SYMBOL_NODE);
450
+
451
+ AST_HTML_ATTRIBUTE_NODE_T* id_attribute =
452
+ id_is_ruby_expression ? create_html_attribute_with_ruby_literal("id", id_value, id_start, id_end, allocator)
453
+ : create_html_attribute_node("id", id_value, id_start, id_end, allocator);
454
+
455
+ if (id_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) id_attribute, allocator); }
456
+
457
+ hb_allocator_dealloc(allocator, id_value);
458
+ }
459
+ }
460
+
461
+ token_T* tag_name_token =
462
+ tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
463
+
464
+ hb_array_T* open_tag_children = attributes ? attributes : hb_array_init(0, allocator);
465
+
466
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
467
+ block_node->tag_opening,
468
+ block_node->content,
469
+ block_node->tag_closing,
470
+ tag_name_token,
471
+ open_tag_children,
472
+ block_node->tag_opening->location.start,
473
+ block_node->tag_closing->location.end,
474
+ hb_array_init(0, allocator),
475
+ allocator
476
+ );
477
+
478
+ hb_array_T* body = block_node->body ? block_node->body : hb_array_init(0, allocator);
479
+ AST_NODE_T* close_tag = (AST_NODE_T*) block_node->end_node;
480
+
481
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
482
+ (AST_NODE_T*) open_tag_node,
483
+ tag_name_token,
484
+ body,
485
+ close_tag,
486
+ false,
487
+ parse_context->matched_handler->source,
488
+ block_node->base.location.start,
489
+ block_node->base.location.end,
490
+ hb_array_init(0, allocator),
491
+ allocator
492
+ );
493
+
494
+ hb_allocator_dealloc(allocator, tag_name);
495
+ return (AST_NODE_T*) element;
496
+ }
497
+
498
+ static AST_NODE_T* transform_link_to_helper(
499
+ AST_ERB_CONTENT_NODE_T* erb_node,
500
+ analyze_ruby_context_T* context,
501
+ tag_helper_parse_context_T* parse_context
502
+ ) {
503
+ if (!erb_node || !context || !parse_context) { return NULL; }
504
+ hb_allocator_T* allocator = context->allocator;
505
+ tag_helper_info_T* info = parse_context->info;
506
+
507
+ char* href = extract_link_to_href(info->call_node, &parse_context->parser, allocator);
508
+
509
+ hb_array_T* attributes = NULL;
510
+ pm_arguments_node_t* link_arguments = info->call_node->arguments;
511
+ bool has_inline_block = info->call_node->block && info->call_node->block->type == PM_BLOCK_NODE;
512
+
513
+ bool second_arg_is_hash = link_arguments && link_arguments->arguments.size == 2
514
+ && (link_arguments->arguments.nodes[1]->type == PM_KEYWORD_HASH_NODE
515
+ || link_arguments->arguments.nodes[1]->type == PM_HASH_NODE);
516
+ bool keyword_hash_is_url = !has_inline_block && second_arg_is_hash;
517
+
518
+ if (!keyword_hash_is_url) {
519
+ attributes = extract_html_attributes_from_call_node(
520
+ info->call_node,
521
+ parse_context->prism_source,
522
+ parse_context->original_source,
523
+ parse_context->erb_content_offset,
524
+ allocator
525
+ );
526
+ }
527
+
528
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
529
+
530
+ if (has_inline_block && link_arguments && link_arguments->arguments.size >= 2) {
531
+ pm_node_t* second_arg = link_arguments->arguments.nodes[1];
532
+
533
+ if (second_arg->type != PM_KEYWORD_HASH_NODE && second_arg->type != PM_HASH_NODE
534
+ && second_arg->type != PM_STRING_NODE && second_arg->type != PM_SYMBOL_NODE) {
535
+ size_t source_length = second_arg->location.end - second_arg->location.start;
536
+ char* content = hb_allocator_strndup(allocator, (const char*) second_arg->location.start, source_length);
537
+
538
+ if (content) {
539
+ position_T position = prism_location_to_position_with_offset(
540
+ &second_arg->location,
541
+ parse_context->original_source,
542
+ parse_context->erb_content_offset,
543
+ parse_context->prism_source
544
+ );
545
+
546
+ AST_RUBY_HTML_ATTRIBUTES_SPLAT_NODE_T* splat_node = ast_ruby_html_attributes_splat_node_init(
547
+ hb_string_from_c_string(content),
548
+ HB_STRING_EMPTY,
549
+ position,
550
+ position,
551
+ hb_array_init(0, allocator),
552
+ allocator
553
+ );
554
+
555
+ if (splat_node) { hb_array_append(attributes, (AST_NODE_T*) splat_node); }
556
+
557
+ hb_allocator_dealloc(allocator, content);
558
+ }
559
+ }
560
+ }
561
+
562
+ // `method:` implies `rel="nofollow"`
563
+ bool has_data_method = false;
564
+ hb_string_T data_method_string = hb_string("data-method");
565
+
566
+ for (size_t i = 0; i < hb_array_size(attributes); i++) {
567
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(attributes, i);
568
+
569
+ if (node->type != AST_HTML_ATTRIBUTE_NODE) { continue; }
570
+
571
+ AST_HTML_ATTRIBUTE_NODE_T* attribute = (AST_HTML_ATTRIBUTE_NODE_T*) node;
572
+
573
+ if (!attribute->name || !attribute->name->children || !hb_array_size(attribute->name->children)) { continue; }
574
+
575
+ AST_LITERAL_NODE_T* literal = (AST_LITERAL_NODE_T*) hb_array_get(attribute->name->children, 0);
576
+
577
+ if (hb_string_equals(literal->content, data_method_string)) {
578
+ has_data_method = true;
579
+ break;
580
+ }
581
+ }
582
+
583
+ if (has_data_method) {
584
+ position_T rel_position = erb_node->base.location.start;
585
+
586
+ AST_HTML_ATTRIBUTE_NODE_T* rel_attribute =
587
+ create_html_attribute_node("rel", "nofollow", rel_position, rel_position, allocator);
588
+
589
+ if (rel_attribute) { hb_array_append(attributes, rel_attribute); }
590
+ }
591
+
592
+ char* href_for_body = NULL;
593
+ bool href_for_body_is_ruby_expression = false;
594
+
595
+ if (href) {
596
+ position_T href_start = erb_node->content->location.start;
597
+ position_T href_end = href_start;
598
+ bool href_is_ruby_expression = true;
599
+
600
+ if (info->call_node && info->call_node->arguments) {
601
+ pm_arguments_node_t* arguments = info->call_node->arguments;
602
+ pm_node_t* href_argument = NULL;
603
+
604
+ if (has_inline_block) {
605
+ if (arguments->arguments.size >= 1) {
606
+ href_argument = arguments->arguments.nodes[0];
607
+ href_is_ruby_expression = (href_argument->type != PM_STRING_NODE);
608
+ }
609
+ } else if (arguments->arguments.size >= 2) {
610
+ href_argument = arguments->arguments.nodes[1];
611
+ href_is_ruby_expression = (href_argument->type != PM_STRING_NODE);
612
+ } else if (arguments->arguments.size == 1) {
613
+ href_argument = arguments->arguments.nodes[0];
614
+ href_is_ruby_expression = true;
615
+ }
616
+
617
+ if (href_argument) {
618
+ prism_node_location_to_positions(&href_argument->location, parse_context, &href_start, &href_end);
619
+ }
620
+ }
621
+
622
+ AST_HTML_ATTRIBUTE_NODE_T* href_attribute =
623
+ create_href_attribute(href, href_is_ruby_expression, href_start, href_end, allocator);
624
+
625
+ if (href_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) href_attribute, allocator); }
626
+ if (!info->content) {
627
+ href_for_body = hb_allocator_strdup(allocator, href);
628
+ href_for_body_is_ruby_expression = href_is_ruby_expression;
629
+ }
630
+
631
+ hb_allocator_dealloc(allocator, href);
632
+ }
633
+
634
+ position_T tag_name_start, tag_name_end;
635
+ calculate_tag_name_positions(
636
+ parse_context,
637
+ erb_node->base.location.start,
638
+ erb_node->base.location.end,
639
+ &tag_name_start,
640
+ &tag_name_end
641
+ );
642
+
643
+ token_T* tag_name_token = create_synthetic_token(allocator, "a", TOKEN_IDENTIFIER, tag_name_start, tag_name_end);
644
+
645
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
646
+ erb_node->tag_opening,
647
+ erb_node->content,
648
+ erb_node->tag_closing,
649
+ tag_name_token,
650
+ attributes,
651
+ erb_node->base.location.start,
652
+ erb_node->base.location.end,
653
+ hb_array_init(0, allocator),
654
+ allocator
655
+ );
656
+
657
+ hb_array_T* body = hb_array_init(1, allocator);
658
+
659
+ if (info->content) {
660
+ bool content_is_ruby_expression = false;
661
+
662
+ if (has_inline_block && info->call_node->block && info->call_node->block->type == PM_BLOCK_NODE) {
663
+ pm_block_node_t* block_node = (pm_block_node_t*) info->call_node->block;
664
+
665
+ if (block_node->body && block_node->body->type == PM_STATEMENTS_NODE) {
666
+ pm_statements_node_t* statements = (pm_statements_node_t*) block_node->body;
667
+
668
+ if (statements->body.size == 1) {
669
+ content_is_ruby_expression = (statements->body.nodes[0]->type != PM_STRING_NODE);
670
+ }
671
+ }
672
+ } else if (info->call_node && info->call_node->arguments && info->call_node->arguments->arguments.size >= 1) {
673
+ pm_node_t* first_argument = info->call_node->arguments->arguments.nodes[0];
674
+ content_is_ruby_expression = (first_argument->type != PM_STRING_NODE);
675
+ }
676
+
677
+ append_body_content_node(
678
+ body,
679
+ info->content,
680
+ content_is_ruby_expression,
681
+ erb_node->base.location.start,
682
+ erb_node->base.location.end,
683
+ allocator
684
+ );
685
+ } else if (href_for_body) {
686
+ append_body_content_node(
687
+ body,
688
+ href_for_body,
689
+ href_for_body_is_ruby_expression,
690
+ erb_node->base.location.start,
691
+ erb_node->base.location.end,
692
+ allocator
693
+ );
694
+
695
+ hb_allocator_dealloc(allocator, href_for_body);
696
+ }
697
+
698
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
699
+ tag_name_token,
700
+ erb_node->base.location.end,
701
+ erb_node->base.location.end,
702
+ hb_array_init(0, allocator),
703
+ allocator
704
+ );
705
+
706
+ AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
707
+ (AST_NODE_T*) open_tag_node,
708
+ tag_name_token,
709
+ body,
710
+ (AST_NODE_T*) virtual_close,
711
+ false,
712
+ parse_context->matched_handler->source,
713
+ erb_node->base.location.start,
714
+ erb_node->base.location.end,
715
+ hb_array_init(0, allocator),
716
+ allocator
717
+ );
718
+
719
+ return (AST_NODE_T*) element;
720
+ }
721
+
722
+ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
723
+ if (!node || !context) { return; }
724
+
725
+ hb_array_T* array = NULL;
726
+
727
+ switch (node->type) {
728
+ case AST_DOCUMENT_NODE: array = ((AST_DOCUMENT_NODE_T*) node)->children; break;
729
+ case AST_HTML_ELEMENT_NODE: array = ((AST_HTML_ELEMENT_NODE_T*) node)->body; break;
730
+ case AST_HTML_OPEN_TAG_NODE: array = ((AST_HTML_OPEN_TAG_NODE_T*) node)->children; break;
731
+ case AST_HTML_ATTRIBUTE_VALUE_NODE: array = ((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children; break;
732
+ case AST_ERB_BLOCK_NODE: array = ((AST_ERB_BLOCK_NODE_T*) node)->body; break;
733
+ default: return;
734
+ }
735
+
736
+ if (!array) { return; }
737
+
738
+ for (size_t i = 0; i < hb_array_size(array); i++) {
739
+ AST_NODE_T* child = hb_array_get(array, i);
740
+ if (!child) { continue; }
741
+
742
+ AST_NODE_T* replacement = NULL;
743
+
744
+ if (child->type == AST_ERB_BLOCK_NODE) {
745
+ AST_ERB_BLOCK_NODE_T* block_node = (AST_ERB_BLOCK_NODE_T*) child;
746
+ token_T* block_content = block_node->content;
747
+
748
+ if (block_content && !hb_string_is_empty(block_content->value)) {
749
+ char* block_string = hb_string_to_c_string_using_malloc(block_content->value);
750
+ size_t erb_content_offset = 0;
751
+
752
+ if (context->source) {
753
+ erb_content_offset = calculate_byte_offset_from_position(context->source, block_content->location.start);
754
+ }
755
+
756
+ tag_helper_parse_context_T* parse_context =
757
+ parse_tag_helper_content(block_string, context->source, erb_content_offset, context->allocator);
758
+
759
+ if (parse_context) {
760
+ replacement = transform_erb_block_to_tag_helper(block_node, context, parse_context);
761
+ free_tag_helper_parse_context(parse_context);
762
+ }
763
+
764
+ free(block_string);
765
+ }
766
+ } else if (child->type == AST_ERB_CONTENT_NODE) {
767
+ AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) child;
768
+ token_T* tag_opening = erb_node->tag_opening;
769
+
770
+ if (tag_opening && !hb_string_is_empty(tag_opening->value)) {
771
+ const char* opening_string = tag_opening->value.data;
772
+
773
+ if (opening_string && strstr(opening_string, "#") != NULL) { continue; }
774
+ }
775
+
776
+ token_T* erb_content = erb_node->content;
777
+
778
+ if (erb_content && !hb_string_is_empty(erb_content->value)) {
779
+ char* erb_string = hb_string_to_c_string_using_malloc(erb_content->value);
780
+ size_t erb_content_offset = 0;
781
+
782
+ if (context->source) {
783
+ erb_content_offset = calculate_byte_offset_from_position(context->source, erb_content->location.start);
784
+ }
785
+
786
+ tag_helper_parse_context_T* parse_context =
787
+ parse_tag_helper_content(erb_string, context->source, erb_content_offset, context->allocator);
788
+
789
+ if (parse_context) {
790
+ if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
791
+ replacement = transform_link_to_helper(erb_node, context, parse_context);
792
+ } else {
793
+ replacement = transform_tag_helper_with_attributes(erb_node, context, parse_context);
794
+ }
795
+
796
+ free_tag_helper_parse_context(parse_context);
797
+ }
798
+
799
+ free(erb_string);
800
+ }
801
+ }
802
+
803
+ if (replacement) { hb_array_set(array, i, replacement); }
804
+ }
805
+ }
806
+
807
+ bool transform_tag_helper_nodes(const AST_NODE_T* node, void* data) {
808
+ analyze_ruby_context_T* context = (analyze_ruby_context_T*) data;
809
+
810
+ transform_tag_helper_blocks(node, context);
811
+
812
+ herb_visit_child_nodes(node, transform_tag_helper_nodes, data);
813
+
814
+ return false;
815
+ }