herb 0.8.10-arm-linux-gnu → 0.9.0-arm-linux-gnu

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 (209) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +11 -3
  3. data/README.md +64 -34
  4. data/Rakefile +48 -40
  5. data/config.yml +317 -34
  6. data/ext/herb/error_helpers.c +367 -140
  7. data/ext/herb/error_helpers.h +1 -0
  8. data/ext/herb/extconf.rb +67 -28
  9. data/ext/herb/extension.c +317 -51
  10. data/ext/herb/extension.h +1 -0
  11. data/ext/herb/extension_helpers.c +23 -14
  12. data/ext/herb/extension_helpers.h +2 -2
  13. data/ext/herb/nodes.c +537 -270
  14. data/ext/herb/nodes.h +1 -0
  15. data/herb.gemspec +3 -2
  16. data/lib/herb/3.0/herb.so +0 -0
  17. data/lib/herb/3.1/herb.so +0 -0
  18. data/lib/herb/3.2/herb.so +0 -0
  19. data/lib/herb/3.3/herb.so +0 -0
  20. data/lib/herb/3.4/herb.so +0 -0
  21. data/lib/herb/4.0/herb.so +0 -0
  22. data/lib/herb/ast/helpers.rb +3 -3
  23. data/lib/herb/ast/node.rb +15 -2
  24. data/lib/herb/ast/nodes.rb +1132 -157
  25. data/lib/herb/bootstrap.rb +87 -0
  26. data/lib/herb/cli.rb +341 -31
  27. data/lib/herb/configuration.rb +248 -0
  28. data/lib/herb/defaults.yml +32 -0
  29. data/lib/herb/engine/compiler.rb +78 -11
  30. data/lib/herb/engine/debug_visitor.rb +13 -3
  31. data/lib/herb/engine/error_formatter.rb +13 -9
  32. data/lib/herb/engine/parser_error_overlay.rb +10 -6
  33. data/lib/herb/engine/validator.rb +8 -3
  34. data/lib/herb/engine/validators/nesting_validator.rb +2 -2
  35. data/lib/herb/engine.rb +82 -35
  36. data/lib/herb/errors.rb +563 -88
  37. data/lib/herb/lex_result.rb +1 -0
  38. data/lib/herb/location.rb +7 -3
  39. data/lib/herb/parse_result.rb +12 -2
  40. data/lib/herb/parser_options.rb +57 -0
  41. data/lib/herb/position.rb +1 -0
  42. data/lib/herb/prism_inspect.rb +116 -0
  43. data/lib/herb/project.rb +923 -331
  44. data/lib/herb/range.rb +1 -0
  45. data/lib/herb/token.rb +7 -1
  46. data/lib/herb/version.rb +1 -1
  47. data/lib/herb/visitor.rb +37 -2
  48. data/lib/herb/warnings.rb +6 -1
  49. data/lib/herb.rb +35 -3
  50. data/sig/herb/ast/helpers.rbs +2 -2
  51. data/sig/herb/ast/node.rbs +12 -2
  52. data/sig/herb/ast/nodes.rbs +641 -128
  53. data/sig/herb/bootstrap.rbs +31 -0
  54. data/sig/herb/configuration.rbs +89 -0
  55. data/sig/herb/engine/compiler.rbs +9 -1
  56. data/sig/herb/engine/debug_visitor.rbs +2 -0
  57. data/sig/herb/engine/validator.rbs +5 -1
  58. data/sig/herb/engine.rbs +17 -3
  59. data/sig/herb/errors.rbs +258 -63
  60. data/sig/herb/location.rbs +4 -0
  61. data/sig/herb/parse_result.rbs +4 -2
  62. data/sig/herb/parser_options.rbs +42 -0
  63. data/sig/herb/position.rbs +1 -0
  64. data/sig/herb/prism_inspect.rbs +28 -0
  65. data/sig/herb/range.rbs +1 -0
  66. data/sig/herb/token.rbs +6 -0
  67. data/sig/herb/visitor.rbs +25 -4
  68. data/sig/herb/warnings.rbs +6 -1
  69. data/sig/herb.rbs +14 -0
  70. data/sig/herb_c_extension.rbs +5 -2
  71. data/sig/serialized_ast_errors.rbs +54 -6
  72. data/sig/serialized_ast_nodes.rbs +60 -6
  73. data/src/analyze/action_view/attribute_extraction_helpers.c +290 -0
  74. data/src/analyze/action_view/content_tag.c +70 -0
  75. data/src/analyze/action_view/link_to.c +143 -0
  76. data/src/analyze/action_view/registry.c +60 -0
  77. data/src/analyze/action_view/tag.c +64 -0
  78. data/src/analyze/action_view/tag_helper_node_builders.c +305 -0
  79. data/src/analyze/action_view/tag_helpers.c +748 -0
  80. data/src/analyze/action_view/turbo_frame_tag.c +88 -0
  81. data/src/analyze/analyze.c +882 -0
  82. data/src/{analyzed_ruby.c → analyze/analyzed_ruby.c} +13 -11
  83. data/src/analyze/builders.c +343 -0
  84. data/src/analyze/conditional_elements.c +594 -0
  85. data/src/analyze/conditional_open_tags.c +640 -0
  86. data/src/analyze/control_type.c +250 -0
  87. data/src/{analyze_helpers.c → analyze/helpers.c} +48 -23
  88. data/src/analyze/invalid_structures.c +193 -0
  89. data/src/{analyze_missing_end.c → analyze/missing_end.c} +33 -22
  90. data/src/analyze/parse_errors.c +84 -0
  91. data/src/analyze/prism_annotate.c +397 -0
  92. data/src/{analyze_transform.c → analyze/transform.c} +17 -3
  93. data/src/ast_node.c +17 -7
  94. data/src/ast_nodes.c +662 -387
  95. data/src/ast_pretty_print.c +190 -6
  96. data/src/errors.c +1076 -520
  97. data/src/extract.c +145 -49
  98. data/src/herb.c +52 -34
  99. data/src/html_util.c +241 -12
  100. data/src/include/analyze/action_view/attribute_extraction_helpers.h +36 -0
  101. data/src/include/analyze/action_view/tag_helper_handler.h +41 -0
  102. data/src/include/analyze/action_view/tag_helper_node_builders.h +70 -0
  103. data/src/include/analyze/action_view/tag_helpers.h +38 -0
  104. data/src/include/{analyze.h → analyze/analyze.h} +14 -4
  105. data/src/include/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
  106. data/src/include/analyze/builders.h +27 -0
  107. data/src/include/analyze/conditional_elements.h +9 -0
  108. data/src/include/analyze/conditional_open_tags.h +9 -0
  109. data/src/include/analyze/control_type.h +14 -0
  110. data/src/include/{analyze_helpers.h → analyze/helpers.h} +4 -2
  111. data/src/include/analyze/invalid_structures.h +11 -0
  112. data/src/include/analyze/prism_annotate.h +16 -0
  113. data/src/include/ast_node.h +11 -5
  114. data/src/include/ast_nodes.h +117 -38
  115. data/src/include/ast_pretty_print.h +5 -0
  116. data/src/include/element_source.h +3 -8
  117. data/src/include/errors.h +148 -55
  118. data/src/include/extract.h +21 -5
  119. data/src/include/herb.h +18 -6
  120. data/src/include/herb_prism_node.h +13 -0
  121. data/src/include/html_util.h +7 -2
  122. data/src/include/io.h +3 -1
  123. data/src/include/lex_helpers.h +29 -0
  124. data/src/include/lexer.h +1 -1
  125. data/src/include/lexer_peek_helpers.h +87 -13
  126. data/src/include/lexer_struct.h +2 -0
  127. data/src/include/location.h +2 -1
  128. data/src/include/parser.h +27 -2
  129. data/src/include/parser_helpers.h +19 -3
  130. data/src/include/pretty_print.h +10 -5
  131. data/src/include/prism_context.h +45 -0
  132. data/src/include/prism_helpers.h +10 -7
  133. data/src/include/prism_serialized.h +12 -0
  134. data/src/include/token.h +16 -4
  135. data/src/include/token_struct.h +10 -3
  136. data/src/include/utf8.h +2 -1
  137. data/src/include/util/hb_allocator.h +78 -0
  138. data/src/include/util/hb_arena.h +6 -1
  139. data/src/include/util/hb_arena_debug.h +12 -1
  140. data/src/include/util/hb_array.h +7 -3
  141. data/src/include/util/hb_buffer.h +6 -4
  142. data/src/include/util/hb_foreach.h +79 -0
  143. data/src/include/util/hb_narray.h +8 -4
  144. data/src/include/util/hb_string.h +56 -9
  145. data/src/include/util.h +6 -3
  146. data/src/include/version.h +1 -1
  147. data/src/io.c +3 -2
  148. data/src/lexer.c +42 -30
  149. data/src/lexer_peek_helpers.c +12 -74
  150. data/src/location.c +2 -2
  151. data/src/main.c +53 -28
  152. data/src/parser.c +783 -247
  153. data/src/parser_helpers.c +110 -23
  154. data/src/parser_match_tags.c +109 -48
  155. data/src/pretty_print.c +29 -24
  156. data/src/prism_helpers.c +30 -27
  157. data/src/ruby_parser.c +2 -0
  158. data/src/token.c +151 -66
  159. data/src/token_matchers.c +0 -1
  160. data/src/utf8.c +7 -6
  161. data/src/util/hb_allocator.c +341 -0
  162. data/src/util/hb_arena.c +81 -56
  163. data/src/util/hb_arena_debug.c +32 -17
  164. data/src/util/hb_array.c +30 -15
  165. data/src/util/hb_buffer.c +17 -21
  166. data/src/util/hb_narray.c +22 -7
  167. data/src/util/hb_string.c +49 -35
  168. data/src/util.c +21 -11
  169. data/src/visitor.c +47 -0
  170. data/templates/ext/herb/error_helpers.c.erb +24 -11
  171. data/templates/ext/herb/error_helpers.h.erb +1 -0
  172. data/templates/ext/herb/nodes.c.erb +50 -16
  173. data/templates/ext/herb/nodes.h.erb +1 -0
  174. data/templates/java/error_helpers.c.erb +1 -1
  175. data/templates/java/nodes.c.erb +30 -8
  176. data/templates/java/org/herb/ast/Errors.java.erb +24 -1
  177. data/templates/java/org/herb/ast/Nodes.java.erb +80 -21
  178. data/templates/javascript/packages/core/src/errors.ts.erb +16 -3
  179. data/templates/javascript/packages/core/src/node-type-guards.ts.erb +3 -1
  180. data/templates/javascript/packages/core/src/nodes.ts.erb +109 -32
  181. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +13 -4
  182. data/templates/javascript/packages/node/extension/nodes.cpp.erb +43 -4
  183. data/templates/lib/herb/ast/nodes.rb.erb +88 -31
  184. data/templates/lib/herb/errors.rb.erb +15 -3
  185. data/templates/lib/herb/visitor.rb.erb +2 -2
  186. data/templates/rust/src/ast/nodes.rs.erb +97 -44
  187. data/templates/rust/src/errors.rs.erb +2 -1
  188. data/templates/rust/src/nodes.rs.erb +167 -15
  189. data/templates/rust/src/union_types.rs.erb +60 -0
  190. data/templates/rust/src/visitor.rs.erb +81 -0
  191. data/templates/src/{analyze_missing_end.c.erb → analyze/missing_end.c.erb} +9 -6
  192. data/templates/src/{analyze_transform.c.erb → analyze/transform.c.erb} +2 -2
  193. data/templates/src/ast_nodes.c.erb +34 -26
  194. data/templates/src/ast_pretty_print.c.erb +24 -5
  195. data/templates/src/errors.c.erb +60 -54
  196. data/templates/src/include/ast_nodes.h.erb +6 -2
  197. data/templates/src/include/ast_pretty_print.h.erb +5 -0
  198. data/templates/src/include/errors.h.erb +15 -11
  199. data/templates/src/include/util/hb_foreach.h.erb +20 -0
  200. data/templates/src/parser_match_tags.c.erb +10 -4
  201. data/templates/src/visitor.c.erb +2 -2
  202. data/templates/template.rb +204 -29
  203. data/templates/wasm/error_helpers.cpp.erb +9 -5
  204. data/templates/wasm/nodes.cpp.erb +41 -4
  205. metadata +57 -16
  206. data/src/analyze.c +0 -1608
  207. data/src/element_source.c +0 -12
  208. data/src/include/util/hb_system.h +0 -9
  209. data/src/util/hb_system.c +0 -30
@@ -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
+ }