herb 0.8.10-arm-linux-gnu → 0.9.1-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 (212) 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 +473 -34
  6. data/ext/herb/error_helpers.c +535 -140
  7. data/ext/herb/error_helpers.h +1 -0
  8. data/ext/herb/extconf.rb +67 -28
  9. data/ext/herb/extension.c +321 -51
  10. data/ext/herb/extension.h +1 -0
  11. data/ext/herb/extension_helpers.c +24 -14
  12. data/ext/herb/extension_helpers.h +2 -2
  13. data/ext/herb/nodes.c +647 -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 +1530 -179
  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 +119 -43
  36. data/lib/herb/errors.rb +808 -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 +62 -0
  41. data/lib/herb/position.rb +1 -0
  42. data/lib/herb/prism_inspect.rb +120 -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 +47 -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 +773 -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 +21 -3
  59. data/sig/herb/errors.rbs +372 -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 +46 -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 +31 -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/rubyvm.rbs +5 -0
  72. data/sig/serialized_ast_errors.rbs +82 -6
  73. data/sig/serialized_ast_nodes.rbs +91 -6
  74. data/src/analyze/action_view/attribute_extraction_helpers.c +303 -0
  75. data/src/analyze/action_view/content_tag.c +78 -0
  76. data/src/analyze/action_view/link_to.c +167 -0
  77. data/src/analyze/action_view/registry.c +83 -0
  78. data/src/analyze/action_view/tag.c +70 -0
  79. data/src/analyze/action_view/tag_helper_node_builders.c +305 -0
  80. data/src/analyze/action_view/tag_helpers.c +815 -0
  81. data/src/analyze/action_view/turbo_frame_tag.c +88 -0
  82. data/src/analyze/analyze.c +885 -0
  83. data/src/{analyzed_ruby.c → analyze/analyzed_ruby.c} +13 -11
  84. data/src/analyze/builders.c +343 -0
  85. data/src/analyze/conditional_elements.c +594 -0
  86. data/src/analyze/conditional_open_tags.c +640 -0
  87. data/src/analyze/control_type.c +250 -0
  88. data/src/{analyze_helpers.c → analyze/helpers.c} +48 -23
  89. data/src/analyze/invalid_structures.c +193 -0
  90. data/src/{analyze_missing_end.c → analyze/missing_end.c} +33 -22
  91. data/src/analyze/parse_errors.c +84 -0
  92. data/src/analyze/prism_annotate.c +399 -0
  93. data/src/analyze/render_nodes.c +761 -0
  94. data/src/{analyze_transform.c → analyze/transform.c} +24 -3
  95. data/src/ast_node.c +17 -7
  96. data/src/ast_nodes.c +759 -387
  97. data/src/ast_pretty_print.c +264 -6
  98. data/src/errors.c +1454 -519
  99. data/src/extract.c +145 -49
  100. data/src/herb.c +52 -34
  101. data/src/html_util.c +241 -12
  102. data/src/include/analyze/action_view/attribute_extraction_helpers.h +36 -0
  103. data/src/include/analyze/action_view/tag_helper_handler.h +43 -0
  104. data/src/include/analyze/action_view/tag_helper_node_builders.h +70 -0
  105. data/src/include/analyze/action_view/tag_helpers.h +38 -0
  106. data/src/include/{analyze.h → analyze/analyze.h} +14 -4
  107. data/src/include/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
  108. data/src/include/analyze/builders.h +27 -0
  109. data/src/include/analyze/conditional_elements.h +9 -0
  110. data/src/include/analyze/conditional_open_tags.h +9 -0
  111. data/src/include/analyze/control_type.h +14 -0
  112. data/src/include/{analyze_helpers.h → analyze/helpers.h} +4 -2
  113. data/src/include/analyze/invalid_structures.h +11 -0
  114. data/src/include/analyze/prism_annotate.h +16 -0
  115. data/src/include/analyze/render_nodes.h +11 -0
  116. data/src/include/ast_node.h +11 -5
  117. data/src/include/ast_nodes.h +154 -38
  118. data/src/include/ast_pretty_print.h +5 -0
  119. data/src/include/element_source.h +3 -8
  120. data/src/include/errors.h +206 -55
  121. data/src/include/extract.h +21 -5
  122. data/src/include/herb.h +18 -6
  123. data/src/include/herb_prism_node.h +13 -0
  124. data/src/include/html_util.h +7 -2
  125. data/src/include/io.h +3 -1
  126. data/src/include/lex_helpers.h +29 -0
  127. data/src/include/lexer.h +1 -1
  128. data/src/include/lexer_peek_helpers.h +87 -13
  129. data/src/include/lexer_struct.h +2 -0
  130. data/src/include/location.h +2 -1
  131. data/src/include/parser.h +28 -2
  132. data/src/include/parser_helpers.h +19 -3
  133. data/src/include/pretty_print.h +10 -5
  134. data/src/include/prism_context.h +45 -0
  135. data/src/include/prism_helpers.h +10 -7
  136. data/src/include/prism_serialized.h +12 -0
  137. data/src/include/token.h +16 -4
  138. data/src/include/token_struct.h +10 -3
  139. data/src/include/utf8.h +2 -1
  140. data/src/include/util/hb_allocator.h +78 -0
  141. data/src/include/util/hb_arena.h +6 -1
  142. data/src/include/util/hb_arena_debug.h +12 -1
  143. data/src/include/util/hb_array.h +7 -3
  144. data/src/include/util/hb_buffer.h +6 -4
  145. data/src/include/util/hb_foreach.h +79 -0
  146. data/src/include/util/hb_narray.h +8 -4
  147. data/src/include/util/hb_string.h +56 -9
  148. data/src/include/util.h +6 -3
  149. data/src/include/version.h +1 -1
  150. data/src/io.c +3 -2
  151. data/src/lexer.c +42 -30
  152. data/src/lexer_peek_helpers.c +12 -74
  153. data/src/location.c +2 -2
  154. data/src/main.c +53 -28
  155. data/src/parser.c +784 -247
  156. data/src/parser_helpers.c +110 -23
  157. data/src/parser_match_tags.c +129 -48
  158. data/src/pretty_print.c +29 -24
  159. data/src/prism_helpers.c +30 -27
  160. data/src/ruby_parser.c +2 -0
  161. data/src/token.c +151 -66
  162. data/src/token_matchers.c +0 -1
  163. data/src/utf8.c +7 -6
  164. data/src/util/hb_allocator.c +341 -0
  165. data/src/util/hb_arena.c +81 -56
  166. data/src/util/hb_arena_debug.c +32 -17
  167. data/src/util/hb_array.c +30 -15
  168. data/src/util/hb_buffer.c +17 -21
  169. data/src/util/hb_narray.c +22 -7
  170. data/src/util/hb_string.c +49 -35
  171. data/src/util.c +21 -11
  172. data/src/visitor.c +67 -0
  173. data/templates/ext/herb/error_helpers.c.erb +24 -11
  174. data/templates/ext/herb/error_helpers.h.erb +1 -0
  175. data/templates/ext/herb/nodes.c.erb +50 -16
  176. data/templates/ext/herb/nodes.h.erb +1 -0
  177. data/templates/java/error_helpers.c.erb +1 -1
  178. data/templates/java/nodes.c.erb +30 -8
  179. data/templates/java/org/herb/ast/Errors.java.erb +24 -1
  180. data/templates/java/org/herb/ast/Nodes.java.erb +80 -21
  181. data/templates/javascript/packages/core/src/errors.ts.erb +16 -3
  182. data/templates/javascript/packages/core/src/node-type-guards.ts.erb +3 -1
  183. data/templates/javascript/packages/core/src/nodes.ts.erb +109 -32
  184. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +13 -4
  185. data/templates/javascript/packages/node/extension/nodes.cpp.erb +43 -4
  186. data/templates/lib/herb/ast/nodes.rb.erb +95 -32
  187. data/templates/lib/herb/errors.rb.erb +15 -3
  188. data/templates/lib/herb/visitor.rb.erb +2 -2
  189. data/templates/rust/src/ast/nodes.rs.erb +97 -44
  190. data/templates/rust/src/errors.rs.erb +2 -1
  191. data/templates/rust/src/nodes.rs.erb +168 -16
  192. data/templates/rust/src/union_types.rs.erb +60 -0
  193. data/templates/rust/src/visitor.rs.erb +81 -0
  194. data/templates/src/{analyze_missing_end.c.erb → analyze/missing_end.c.erb} +9 -6
  195. data/templates/src/{analyze_transform.c.erb → analyze/transform.c.erb} +2 -2
  196. data/templates/src/ast_nodes.c.erb +34 -26
  197. data/templates/src/ast_pretty_print.c.erb +24 -5
  198. data/templates/src/errors.c.erb +60 -54
  199. data/templates/src/include/ast_nodes.h.erb +6 -2
  200. data/templates/src/include/ast_pretty_print.h.erb +5 -0
  201. data/templates/src/include/errors.h.erb +15 -11
  202. data/templates/src/include/util/hb_foreach.h.erb +20 -0
  203. data/templates/src/parser_match_tags.c.erb +10 -4
  204. data/templates/src/visitor.c.erb +2 -2
  205. data/templates/template.rb +204 -29
  206. data/templates/wasm/error_helpers.cpp.erb +9 -5
  207. data/templates/wasm/nodes.cpp.erb +41 -4
  208. metadata +60 -16
  209. data/src/analyze.c +0 -1608
  210. data/src/element_source.c +0 -12
  211. data/src/include/util/hb_system.h +0 -9
  212. data/src/util/hb_system.c +0 -30
@@ -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
+ }