@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,761 @@
1
+ #include "../include/analyze/render_nodes.h"
2
+ #include "../include/analyze/action_view/tag_helper_node_builders.h"
3
+ #include "../include/analyze/action_view/tag_helpers.h"
4
+ #include "../include/analyze/analyze.h"
5
+ #include "../include/ast_nodes.h"
6
+ #include "../include/errors.h"
7
+ #include "../include/util/hb_allocator.h"
8
+ #include "../include/util/hb_array.h"
9
+ #include "../include/util/hb_string.h"
10
+ #include "../include/util/string.h"
11
+ #include "../include/visitor.h"
12
+
13
+ #include <prism.h>
14
+ #include <stdbool.h>
15
+ #include <string.h>
16
+
17
+ typedef struct {
18
+ char* value;
19
+ pm_node_t* value_node;
20
+ } keyword_result_T;
21
+
22
+ typedef struct {
23
+ const char* name;
24
+ token_T** target;
25
+ } keyword_field_T;
26
+
27
+ // actionview/lib/action_view/render_parser.rb
28
+ static const char* render_keywords[] = {
29
+ "partial", "template", "layout", "file", "inline", "body", "plain",
30
+ "html", "renderable", "locals", "collection", "object", "as", "spacer_template",
31
+ "formats", "variants", "handlers", "status", "content_type", "location", NULL
32
+ };
33
+
34
+ static bool is_render_call(pm_call_node_t* call_node, pm_parser_t* parser) {
35
+ if (!call_node || !call_node->name) { return false; }
36
+
37
+ pm_constant_t* constant = pm_constant_pool_id_to_constant(&parser->constant_pool, call_node->name);
38
+
39
+ return constant && constant->length == 6 && strncmp((const char*) constant->start, "render", 6) == 0;
40
+ }
41
+
42
+ static pm_call_node_t* find_render_call(pm_node_t* node, pm_parser_t* parser) {
43
+ if (!node) { return NULL; }
44
+
45
+ if (node->type == PM_CALL_NODE) {
46
+ pm_call_node_t* call_node = (pm_call_node_t*) node;
47
+ if (is_render_call(call_node, parser)) { return call_node; }
48
+ }
49
+
50
+ if (node->type == PM_PROGRAM_NODE) {
51
+ pm_program_node_t* program = (pm_program_node_t*) node;
52
+ if (program->statements && program->statements->body.size > 0) {
53
+ return find_render_call(program->statements->body.nodes[0], parser);
54
+ }
55
+ }
56
+
57
+ if (node->type == PM_STATEMENTS_NODE) {
58
+ pm_statements_node_t* statements = (pm_statements_node_t*) node;
59
+ if (statements->body.size > 0) { return find_render_call(statements->body.nodes[0], parser); }
60
+ }
61
+
62
+ return NULL;
63
+ }
64
+
65
+ static char* extract_string_value(pm_node_t* node, hb_allocator_T* allocator) {
66
+ if (!node) { return NULL; }
67
+
68
+ if (node->type == PM_STRING_NODE) {
69
+ pm_string_node_t* string_node = (pm_string_node_t*) node;
70
+ size_t length = pm_string_length(&string_node->unescaped);
71
+ return hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
72
+ }
73
+
74
+ if (node->type == PM_SYMBOL_NODE) {
75
+ pm_symbol_node_t* symbol_node = (pm_symbol_node_t*) node;
76
+ size_t length = pm_string_length(&symbol_node->unescaped);
77
+ return hb_allocator_strndup(allocator, (const char*) pm_string_source(&symbol_node->unescaped), length);
78
+ }
79
+
80
+ return NULL;
81
+ }
82
+
83
+ static char* extract_source_expression(pm_node_t* node, hb_allocator_T* allocator) {
84
+ if (!node) { return NULL; }
85
+
86
+ size_t length = node->location.end - node->location.start;
87
+ return hb_allocator_strndup(allocator, (const char*) node->location.start, length);
88
+ }
89
+
90
+ static token_T* create_token_from_prism_node(
91
+ pm_node_t* node,
92
+ const char* value,
93
+ token_type_T type,
94
+ const char* source,
95
+ size_t erb_content_offset,
96
+ const uint8_t* erb_content_source,
97
+ hb_allocator_T* allocator
98
+ ) {
99
+ position_T start = { .line = 1, .column = 1 };
100
+ position_T end = { .line = 1, .column = 1 };
101
+
102
+ if (node && source && erb_content_source) {
103
+ size_t start_offset_in_erb = (size_t) (node->location.start - erb_content_source);
104
+ size_t end_offset_in_erb = (size_t) (node->location.end - erb_content_source);
105
+
106
+ size_t total_start = erb_content_offset + start_offset_in_erb;
107
+ size_t total_end = erb_content_offset + end_offset_in_erb;
108
+
109
+ start = byte_offset_to_position(source, total_start);
110
+ end = byte_offset_to_position(source, total_end);
111
+ }
112
+
113
+ return create_synthetic_token(allocator, value, type, start, end);
114
+ }
115
+
116
+ static pm_keyword_hash_node_t* find_keyword_hash(pm_arguments_node_t* arguments) {
117
+ if (!arguments || arguments->arguments.size == 0) { return NULL; }
118
+
119
+ pm_node_t* last = arguments->arguments.nodes[arguments->arguments.size - 1];
120
+ if (last->type == PM_KEYWORD_HASH_NODE) { return (pm_keyword_hash_node_t*) last; }
121
+
122
+ return NULL;
123
+ }
124
+
125
+ static keyword_result_T find_keyword_value(
126
+ pm_keyword_hash_node_t* keyword_hash,
127
+ const char* key,
128
+ hb_allocator_T* allocator
129
+ ) {
130
+ keyword_result_T result = { .value = NULL, .value_node = NULL };
131
+ if (!keyword_hash) { return result; }
132
+
133
+ for (size_t index = 0; index < keyword_hash->elements.size; index++) {
134
+ pm_node_t* element = keyword_hash->elements.nodes[index];
135
+ if (element->type != PM_ASSOC_NODE) { continue; }
136
+
137
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
138
+ char* assoc_key = extract_string_value(assoc->key, allocator);
139
+
140
+ if (assoc_key && string_equals(assoc_key, key)) {
141
+ hb_allocator_dealloc(allocator, assoc_key);
142
+
143
+ result.value_node = assoc->value;
144
+
145
+ char* string_value = extract_string_value(assoc->value, allocator);
146
+
147
+ if (string_value) {
148
+ result.value = string_value;
149
+ } else {
150
+ result.value = extract_source_expression(assoc->value, allocator);
151
+ }
152
+
153
+ return result;
154
+ }
155
+
156
+ if (assoc_key) { hb_allocator_dealloc(allocator, assoc_key); }
157
+ }
158
+
159
+ return result;
160
+ }
161
+
162
+ static pm_hash_node_t* find_locals_hash(pm_keyword_hash_node_t* keyword_hash, hb_allocator_T* allocator) {
163
+ if (!keyword_hash) { return NULL; }
164
+
165
+ for (size_t index = 0; index < keyword_hash->elements.size; index++) {
166
+ pm_node_t* element = keyword_hash->elements.nodes[index];
167
+ if (element->type != PM_ASSOC_NODE) { continue; }
168
+
169
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
170
+ char* key = extract_string_value(assoc->key, allocator);
171
+
172
+ if (key && string_equals(key, "locals")) {
173
+ hb_allocator_dealloc(allocator, key);
174
+ if (assoc->value->type == PM_HASH_NODE) { return (pm_hash_node_t*) assoc->value; }
175
+ return NULL;
176
+ }
177
+
178
+ if (key) { hb_allocator_dealloc(allocator, key); }
179
+ }
180
+
181
+ return NULL;
182
+ }
183
+
184
+ static hb_array_T* extract_locals_from_hash(
185
+ pm_hash_node_t* hash,
186
+ const char* source,
187
+ size_t erb_content_offset,
188
+ const uint8_t* erb_content_source,
189
+ hb_allocator_T* allocator
190
+ ) {
191
+ hb_array_T* locals = hb_array_init(hash->elements.size, allocator);
192
+
193
+ for (size_t index = 0; index < hash->elements.size; index++) {
194
+ pm_node_t* element = hash->elements.nodes[index];
195
+ if (element->type != PM_ASSOC_NODE) { continue; }
196
+
197
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
198
+ char* name = extract_string_value(assoc->key, allocator);
199
+ if (!name) { name = extract_source_expression(assoc->key, allocator); }
200
+ if (!name) { continue; }
201
+
202
+ char* value = extract_source_expression(assoc->value, allocator);
203
+
204
+ if (!value) {
205
+ hb_allocator_dealloc(allocator, name);
206
+ continue;
207
+ }
208
+
209
+ token_T* name_token = create_token_from_prism_node(
210
+ assoc->key,
211
+ name,
212
+ TOKEN_IDENTIFIER,
213
+ source,
214
+ erb_content_offset,
215
+ erb_content_source,
216
+ allocator
217
+ );
218
+
219
+ position_T value_start = { .line = 1, .column = 1 };
220
+ position_T value_end = value_start;
221
+
222
+ if (assoc->value && source && erb_content_source) {
223
+ size_t start_offset = (size_t) (assoc->value->location.start - erb_content_source);
224
+ size_t end_offset = (size_t) (assoc->value->location.end - erb_content_source);
225
+ value_start = byte_offset_to_position(source, erb_content_offset + start_offset);
226
+ value_end = byte_offset_to_position(source, erb_content_offset + end_offset);
227
+ }
228
+
229
+ AST_RUBY_LITERAL_NODE_T* value_node = ast_ruby_literal_node_init(
230
+ hb_string_from_c_string(value),
231
+ value_start,
232
+ value_end,
233
+ hb_array_init(0, allocator),
234
+ allocator
235
+ );
236
+
237
+ position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
238
+ position_T end = value_end;
239
+
240
+ AST_RUBY_RENDER_LOCAL_NODE_T* local_node =
241
+ ast_ruby_render_local_node_init(name_token, value_node, start, end, hb_array_init(0, allocator), allocator);
242
+
243
+ hb_array_append(locals, local_node);
244
+
245
+ hb_allocator_dealloc(allocator, name);
246
+ hb_allocator_dealloc(allocator, value);
247
+ }
248
+
249
+ return locals;
250
+ }
251
+
252
+ static token_T* extract_keyword_token(
253
+ pm_keyword_hash_node_t* keyword_hash,
254
+ const char* keyword_name,
255
+ const char* source,
256
+ size_t erb_content_offset,
257
+ const uint8_t* erb_content_source,
258
+ hb_allocator_T* allocator
259
+ ) {
260
+ keyword_result_T keyword = find_keyword_value(keyword_hash, keyword_name, allocator);
261
+ if (!keyword.value) { return NULL; }
262
+
263
+ token_T* token = create_token_from_prism_node(
264
+ keyword.value_node,
265
+ keyword.value,
266
+ TOKEN_IDENTIFIER,
267
+ source,
268
+ erb_content_offset,
269
+ erb_content_source,
270
+ allocator
271
+ );
272
+
273
+ hb_allocator_dealloc(allocator, keyword.value);
274
+ return token;
275
+ }
276
+
277
+ static AST_ERB_RENDER_NODE_T* create_render_node_from_call(
278
+ AST_ERB_CONTENT_NODE_T* erb_node,
279
+ pm_call_node_t* call_node,
280
+ pm_parser_t* parser,
281
+ const char* source,
282
+ size_t erb_content_offset,
283
+ const uint8_t* erb_content_source,
284
+ hb_allocator_T* allocator
285
+ ) {
286
+ (void) parser;
287
+
288
+ pm_arguments_node_t* arguments = call_node->arguments;
289
+ pm_keyword_hash_node_t* keyword_hash = arguments ? find_keyword_hash(arguments) : NULL;
290
+
291
+ token_T* partial = NULL;
292
+ token_T* template_path = NULL;
293
+ token_T* layout = NULL;
294
+ token_T* file = NULL;
295
+ token_T* inline_template = NULL;
296
+ token_T* body = NULL;
297
+ token_T* plain = NULL;
298
+ token_T* html = NULL;
299
+ token_T* renderable = NULL;
300
+ token_T* collection = NULL;
301
+ token_T* object = NULL;
302
+ token_T* as_name = NULL;
303
+ token_T* spacer_template = NULL;
304
+ token_T* formats = NULL;
305
+ token_T* variants = NULL;
306
+ token_T* handlers = NULL;
307
+ token_T* content_type = NULL;
308
+
309
+ hb_array_T* locals = NULL;
310
+ bool has_keyword_partial = false;
311
+ bool has_positional_partial = false;
312
+
313
+ if (arguments && arguments->arguments.size >= 1) {
314
+ pm_node_t* first_argument = arguments->arguments.nodes[0];
315
+
316
+ if (first_argument->type == PM_STRING_NODE) {
317
+ char* partial_string = extract_string_value(first_argument, allocator);
318
+
319
+ if (partial_string) {
320
+ has_positional_partial = true;
321
+
322
+ partial = create_token_from_prism_node(
323
+ first_argument,
324
+ partial_string,
325
+ TOKEN_IDENTIFIER,
326
+ source,
327
+ erb_content_offset,
328
+ erb_content_source,
329
+ allocator
330
+ );
331
+
332
+ hb_allocator_dealloc(allocator, partial_string);
333
+ }
334
+ } else if (first_argument->type != PM_KEYWORD_HASH_NODE) {
335
+ char* object_string = extract_source_expression(first_argument, allocator);
336
+
337
+ if (object_string) {
338
+ object = create_token_from_prism_node(
339
+ first_argument,
340
+ object_string,
341
+ TOKEN_IDENTIFIER,
342
+ source,
343
+ erb_content_offset,
344
+ erb_content_source,
345
+ allocator
346
+ );
347
+ hb_allocator_dealloc(allocator, object_string);
348
+ }
349
+ }
350
+ }
351
+
352
+ if (keyword_hash) {
353
+ if (has_positional_partial) {
354
+ keyword_result_T partial_kw = find_keyword_value(keyword_hash, "partial", allocator);
355
+ if (partial_kw.value) { has_keyword_partial = true; }
356
+
357
+ pm_hash_node_t* locals_hash = find_locals_hash(keyword_hash, allocator);
358
+
359
+ if (locals_hash) {
360
+ locals = extract_locals_from_hash(locals_hash, source, erb_content_offset, erb_content_source, allocator);
361
+ } else {
362
+ locals = hb_array_init(keyword_hash->elements.size, allocator);
363
+
364
+ for (size_t index = 0; index < keyword_hash->elements.size; index++) {
365
+ pm_node_t* element = keyword_hash->elements.nodes[index];
366
+ if (element->type != PM_ASSOC_NODE) { continue; }
367
+
368
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
369
+ char* name = extract_string_value(assoc->key, allocator);
370
+ if (!name) { name = extract_source_expression(assoc->key, allocator); }
371
+ if (!name) { continue; }
372
+
373
+ char* value = extract_source_expression(assoc->value, allocator);
374
+
375
+ if (!value) {
376
+ hb_allocator_dealloc(allocator, name);
377
+ continue;
378
+ }
379
+
380
+ token_T* name_token = create_token_from_prism_node(
381
+ assoc->key,
382
+ name,
383
+ TOKEN_IDENTIFIER,
384
+ source,
385
+ erb_content_offset,
386
+ erb_content_source,
387
+ allocator
388
+ );
389
+
390
+ position_T value_start = { .line = 1, .column = 1 };
391
+ position_T value_end = value_start;
392
+
393
+ if (assoc->value && source && erb_content_source) {
394
+ size_t start_offset = (size_t) (assoc->value->location.start - erb_content_source);
395
+ size_t end_offset = (size_t) (assoc->value->location.end - erb_content_source);
396
+ value_start = byte_offset_to_position(source, erb_content_offset + start_offset);
397
+ value_end = byte_offset_to_position(source, erb_content_offset + end_offset);
398
+ }
399
+
400
+ AST_RUBY_LITERAL_NODE_T* value_node = ast_ruby_literal_node_init(
401
+ hb_string_from_c_string(value),
402
+ value_start,
403
+ value_end,
404
+ hb_array_init(0, allocator),
405
+ allocator
406
+ );
407
+
408
+ position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
409
+
410
+ AST_RUBY_RENDER_LOCAL_NODE_T* local_node = ast_ruby_render_local_node_init(
411
+ name_token,
412
+ value_node,
413
+ start,
414
+ value_end,
415
+ hb_array_init(0, allocator),
416
+ allocator
417
+ );
418
+
419
+ hb_array_append(locals, local_node);
420
+ hb_allocator_dealloc(allocator, name);
421
+ hb_allocator_dealloc(allocator, value);
422
+ }
423
+ }
424
+ } else {
425
+ keyword_field_T keyword_fields[] = {
426
+ { "partial", &partial },
427
+ { "template", &template_path },
428
+ { "layout", &layout },
429
+ { "file", &file },
430
+ { "inline", &inline_template },
431
+ { "body", &body },
432
+ { "plain", &plain },
433
+ { "html", &html },
434
+ { "renderable", &renderable },
435
+ { "collection", &collection },
436
+ { "object", &object },
437
+ { "as", &as_name },
438
+ { "spacer_template", &spacer_template },
439
+ { "formats", &formats },
440
+ { "variants", &variants },
441
+ { "handlers", &handlers },
442
+ { "content_type", &content_type },
443
+ };
444
+
445
+ for (size_t index = 0; index < sizeof(keyword_fields) / sizeof(keyword_fields[0]); index++) {
446
+ *keyword_fields[index].target = extract_keyword_token(
447
+ keyword_hash,
448
+ keyword_fields[index].name,
449
+ source,
450
+ erb_content_offset,
451
+ erb_content_source,
452
+ allocator
453
+ );
454
+ }
455
+
456
+ if (partial) { has_keyword_partial = true; }
457
+
458
+ pm_hash_node_t* locals_hash = find_locals_hash(keyword_hash, allocator);
459
+
460
+ if (locals_hash) {
461
+ locals = extract_locals_from_hash(locals_hash, source, erb_content_offset, erb_content_source, allocator);
462
+ }
463
+ }
464
+ }
465
+
466
+ if (!locals) { locals = hb_array_init(0, allocator); }
467
+
468
+ hb_array_T* errors = hb_array_init(0, allocator);
469
+
470
+ if (!has_keyword_partial && partial && keyword_hash) {
471
+ keyword_result_T locals_keyword = find_keyword_value(keyword_hash, "locals", allocator);
472
+
473
+ if (locals_keyword.value) {
474
+ hb_string_T partial_name = partial->value;
475
+
476
+ append_render_ambiguous_locals_error(
477
+ partial_name,
478
+ erb_node->base.location.start,
479
+ erb_node->base.location.end,
480
+ allocator,
481
+ errors
482
+ );
483
+
484
+ hb_allocator_dealloc(allocator, locals_keyword.value);
485
+ }
486
+ }
487
+
488
+ if (has_keyword_partial && partial && keyword_hash && hb_array_size(locals) == 0) {
489
+ bool has_non_render_kwargs = false;
490
+ size_t keywords_length = 0;
491
+
492
+ for (size_t index = 0; index < keyword_hash->elements.size; index++) {
493
+ pm_node_t* element = keyword_hash->elements.nodes[index];
494
+ if (element->type != PM_ASSOC_NODE) { continue; }
495
+
496
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
497
+ char* key = extract_string_value(assoc->key, allocator);
498
+ if (!key) { continue; }
499
+
500
+ bool is_known = false;
501
+
502
+ for (size_t keyword_index = 0; render_keywords[keyword_index] != NULL; keyword_index++) {
503
+ if (string_equals(key, render_keywords[keyword_index])) {
504
+ is_known = true;
505
+ break;
506
+ }
507
+ }
508
+
509
+ if (!is_known) {
510
+ has_non_render_kwargs = true;
511
+ keywords_length += strlen(key) + 2;
512
+ }
513
+
514
+ hb_allocator_dealloc(allocator, key);
515
+ }
516
+
517
+ if (has_non_render_kwargs) {
518
+ char* keywords_buffer = hb_allocator_alloc(allocator, keywords_length + 1);
519
+ keywords_buffer[0] = '\0';
520
+ bool first = true;
521
+
522
+ for (size_t index = 0; index < keyword_hash->elements.size; index++) {
523
+ pm_node_t* element = keyword_hash->elements.nodes[index];
524
+ if (element->type != PM_ASSOC_NODE) { continue; }
525
+
526
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
527
+ char* key = extract_string_value(assoc->key, allocator);
528
+ if (!key) { continue; }
529
+
530
+ bool is_known = false;
531
+
532
+ for (size_t keyword_index = 0; render_keywords[keyword_index] != NULL; keyword_index++) {
533
+ if (string_equals(key, render_keywords[keyword_index])) {
534
+ is_known = true;
535
+ break;
536
+ }
537
+ }
538
+
539
+ if (!is_known) {
540
+ if (!first) { strcat(keywords_buffer, ", "); }
541
+ strcat(keywords_buffer, key);
542
+ first = false;
543
+ }
544
+
545
+ hb_allocator_dealloc(allocator, key);
546
+ }
547
+
548
+ append_render_missing_locals_error(
549
+ partial->value,
550
+ hb_string_from_c_string(keywords_buffer),
551
+ erb_node->base.location.start,
552
+ erb_node->base.location.end,
553
+ allocator,
554
+ errors
555
+ );
556
+
557
+ hb_allocator_dealloc(allocator, keywords_buffer);
558
+ }
559
+ }
560
+
561
+ if (!partial && !template_path && !layout && !file && !inline_template && !body && !plain && !html && !renderable
562
+ && !has_positional_partial && !object) {
563
+ append_render_no_arguments_error(erb_node->base.location.start, erb_node->base.location.end, allocator, errors);
564
+ }
565
+
566
+ if (has_positional_partial && has_keyword_partial && keyword_hash) {
567
+ keyword_result_T keyword_partial = find_keyword_value(keyword_hash, "partial", allocator);
568
+
569
+ append_render_conflicting_partial_error(
570
+ partial->value,
571
+ keyword_partial.value ? hb_string_from_c_string(keyword_partial.value) : hb_string(""),
572
+ erb_node->base.location.start,
573
+ erb_node->base.location.end,
574
+ allocator,
575
+ errors
576
+ );
577
+
578
+ if (keyword_partial.value) { hb_allocator_dealloc(allocator, keyword_partial.value); }
579
+ }
580
+
581
+ if (as_name) {
582
+ const char* as_value = as_name->value.data;
583
+ uint32_t as_length = as_name->value.length;
584
+ bool as_valid = as_length > 0;
585
+
586
+ if (as_valid) {
587
+ char first_char = as_value[0];
588
+ as_valid = (first_char >= 'a' && first_char <= 'z') || first_char == '_';
589
+ }
590
+
591
+ for (uint32_t char_index = 1; as_valid && char_index < as_length; char_index++) {
592
+ char character = as_value[char_index];
593
+ as_valid = (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')
594
+ || (character >= '0' && character <= '9') || character == '_';
595
+ }
596
+
597
+ if (!as_valid) {
598
+ append_render_invalid_as_option_error(
599
+ as_name->value,
600
+ erb_node->base.location.start,
601
+ erb_node->base.location.end,
602
+ allocator,
603
+ errors
604
+ );
605
+ }
606
+ }
607
+
608
+ if (object && collection) {
609
+ append_render_object_and_collection_error(
610
+ erb_node->base.location.start,
611
+ erb_node->base.location.end,
612
+ allocator,
613
+ errors
614
+ );
615
+ }
616
+
617
+ if (layout && !partial && !template_path) {
618
+ append_render_layout_without_block_error(
619
+ layout->value,
620
+ erb_node->base.location.start,
621
+ erb_node->base.location.end,
622
+ allocator,
623
+ errors
624
+ );
625
+ }
626
+
627
+ herb_prism_node_T prism_node = erb_node->prism_node;
628
+
629
+ AST_ERB_RENDER_NODE_T* render_node = ast_erb_render_node_init(
630
+ erb_node->tag_opening,
631
+ erb_node->content,
632
+ erb_node->tag_closing,
633
+ erb_node->analyzed_ruby,
634
+ prism_node,
635
+ partial,
636
+ template_path,
637
+ layout,
638
+ file,
639
+ inline_template,
640
+ body,
641
+ plain,
642
+ html,
643
+ renderable,
644
+ collection,
645
+ object,
646
+ as_name,
647
+ spacer_template,
648
+ formats,
649
+ variants,
650
+ handlers,
651
+ content_type,
652
+ locals,
653
+ erb_node->base.location.start,
654
+ erb_node->base.location.end,
655
+ errors,
656
+ allocator
657
+ );
658
+
659
+ return render_node;
660
+ }
661
+
662
+ static size_t calculate_byte_offset_from_pos(const char* source, position_T position) {
663
+ if (!source) { return 0; }
664
+
665
+ size_t offset = 0;
666
+ uint32_t line = 1;
667
+ uint32_t column = 1;
668
+
669
+ while (source[offset] != '\0') {
670
+ if (line == position.line && column == position.column) { return offset; }
671
+
672
+ if (source[offset] == '\n') {
673
+ line++;
674
+ column = 1;
675
+ } else {
676
+ column++;
677
+ }
678
+
679
+ offset++;
680
+ }
681
+
682
+ return offset;
683
+ }
684
+
685
+ static void transform_render_nodes_in_array(hb_array_T* array, analyze_ruby_context_T* context) {
686
+ if (!array) { return; }
687
+
688
+ for (size_t index = 0; index < hb_array_size(array); index++) {
689
+ AST_NODE_T* child = hb_array_get(array, index);
690
+ if (!child || child->type != AST_ERB_CONTENT_NODE) { continue; }
691
+
692
+ AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) child;
693
+
694
+ if (!erb_node->analyzed_ruby || !erb_node->analyzed_ruby->valid || !erb_node->analyzed_ruby->parsed) { continue; }
695
+
696
+ token_T* tag_opening = erb_node->tag_opening;
697
+
698
+ if (tag_opening && !hb_string_is_empty(tag_opening->value)) {
699
+ const char* opening_string = tag_opening->value.data;
700
+ if (opening_string && strstr(opening_string, "#") != NULL) { continue; }
701
+ }
702
+
703
+ pm_call_node_t* render_call = find_render_call(erb_node->analyzed_ruby->root, &erb_node->analyzed_ruby->parser);
704
+ if (!render_call) { continue; }
705
+
706
+ size_t erb_content_offset = 0;
707
+ if (context->source && erb_node->content) {
708
+ erb_content_offset = calculate_byte_offset_from_pos(context->source, erb_node->content->location.start);
709
+ }
710
+
711
+ const uint8_t* erb_content_source = (const uint8_t*) erb_node->analyzed_ruby->parser.start;
712
+
713
+ AST_ERB_RENDER_NODE_T* render_node = create_render_node_from_call(
714
+ erb_node,
715
+ render_call,
716
+ &erb_node->analyzed_ruby->parser,
717
+ context->source,
718
+ erb_content_offset,
719
+ erb_content_source,
720
+ context->allocator
721
+ );
722
+
723
+ if (render_node) { hb_array_set(array, index, render_node); }
724
+ }
725
+ }
726
+
727
+ static void transform_render_in_node(const AST_NODE_T* node, analyze_ruby_context_T* context) {
728
+ if (!node || !context) { return; }
729
+
730
+ hb_array_T* array = NULL;
731
+
732
+ switch (node->type) {
733
+ case AST_DOCUMENT_NODE: array = ((AST_DOCUMENT_NODE_T*) node)->children; break;
734
+ case AST_HTML_ELEMENT_NODE: array = ((AST_HTML_ELEMENT_NODE_T*) node)->body; break;
735
+ case AST_ERB_BLOCK_NODE: array = ((AST_ERB_BLOCK_NODE_T*) node)->body; break;
736
+ case AST_ERB_IF_NODE: array = ((AST_ERB_IF_NODE_T*) node)->statements; break;
737
+ case AST_ERB_UNLESS_NODE: array = ((AST_ERB_UNLESS_NODE_T*) node)->statements; break;
738
+ case AST_ERB_ELSE_NODE: array = ((AST_ERB_ELSE_NODE_T*) node)->statements; break;
739
+ case AST_ERB_WHILE_NODE: array = ((AST_ERB_WHILE_NODE_T*) node)->statements; break;
740
+ case AST_ERB_UNTIL_NODE: array = ((AST_ERB_UNTIL_NODE_T*) node)->statements; break;
741
+ case AST_ERB_FOR_NODE: array = ((AST_ERB_FOR_NODE_T*) node)->statements; break;
742
+ case AST_ERB_BEGIN_NODE: array = ((AST_ERB_BEGIN_NODE_T*) node)->statements; break;
743
+ case AST_ERB_RESCUE_NODE: array = ((AST_ERB_RESCUE_NODE_T*) node)->statements; break;
744
+ case AST_ERB_ENSURE_NODE: array = ((AST_ERB_ENSURE_NODE_T*) node)->statements; break;
745
+ case AST_ERB_CASE_NODE: array = ((AST_ERB_CASE_NODE_T*) node)->children; break;
746
+ case AST_ERB_WHEN_NODE: array = ((AST_ERB_WHEN_NODE_T*) node)->statements; break;
747
+ default: return;
748
+ }
749
+
750
+ transform_render_nodes_in_array(array, context);
751
+ }
752
+
753
+ bool transform_render_nodes(const AST_NODE_T* node, void* data) {
754
+ analyze_ruby_context_T* context = (analyze_ruby_context_T*) data;
755
+
756
+ transform_render_in_node(node, context);
757
+
758
+ herb_visit_child_nodes(node, transform_render_nodes, data);
759
+
760
+ return false;
761
+ }