@herb-tools/node 0.9.0 → 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 (44) hide show
  1. package/binding.gyp +1 -0
  2. package/dist/herb-node.cjs +1 -1
  3. package/dist/herb-node.esm.js +1 -1
  4. package/extension/error_helpers.cpp +204 -1
  5. package/extension/error_helpers.h +8 -1
  6. package/extension/extension_helpers.cpp +3 -1
  7. package/extension/herb.cpp +11 -0
  8. package/extension/libherb/analyze/action_view/attribute_extraction_helpers.c +14 -1
  9. package/extension/libherb/analyze/action_view/content_tag.c +19 -11
  10. package/extension/libherb/analyze/action_view/link_to.c +25 -1
  11. package/extension/libherb/analyze/action_view/registry.c +23 -0
  12. package/extension/libherb/analyze/action_view/tag.c +14 -8
  13. package/extension/libherb/analyze/action_view/tag_helper_handler.h +2 -0
  14. package/extension/libherb/analyze/action_view/tag_helpers.c +78 -11
  15. package/extension/libherb/analyze/analyze.c +3 -0
  16. package/extension/libherb/analyze/missing_end.c +1 -1
  17. package/extension/libherb/analyze/prism_annotate.c +4 -2
  18. package/extension/libherb/analyze/render_nodes.c +761 -0
  19. package/extension/libherb/analyze/render_nodes.h +11 -0
  20. package/extension/libherb/analyze/transform.c +8 -1
  21. package/extension/libherb/ast_nodes.c +98 -1
  22. package/extension/libherb/ast_nodes.h +38 -1
  23. package/extension/libherb/ast_pretty_print.c +75 -1
  24. package/extension/libherb/ast_pretty_print.h +1 -1
  25. package/extension/libherb/errors.c +380 -1
  26. package/extension/libherb/errors.h +59 -1
  27. package/extension/libherb/include/analyze/action_view/tag_helper_handler.h +2 -0
  28. package/extension/libherb/include/analyze/render_nodes.h +11 -0
  29. package/extension/libherb/include/ast_nodes.h +38 -1
  30. package/extension/libherb/include/ast_pretty_print.h +1 -1
  31. package/extension/libherb/include/errors.h +59 -1
  32. package/extension/libherb/include/parser.h +1 -0
  33. package/extension/libherb/include/util/hb_foreach.h +1 -1
  34. package/extension/libherb/include/version.h +1 -1
  35. package/extension/libherb/parser.c +1 -0
  36. package/extension/libherb/parser.h +1 -0
  37. package/extension/libherb/parser_match_tags.c +21 -1
  38. package/extension/libherb/util/hb_foreach.h +1 -1
  39. package/extension/libherb/version.h +1 -1
  40. package/extension/libherb/visitor.c +21 -1
  41. package/extension/nodes.cpp +143 -1
  42. package/extension/nodes.h +3 -1
  43. package/package.json +2 -2
  44. package/CHANGELOG.md +0 -19
@@ -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
+ }