herb 0.9.0-arm-linux-gnu → 0.9.2-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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/config.yml +156 -0
  3. data/ext/herb/error_helpers.c +168 -0
  4. data/ext/herb/extension.c +4 -0
  5. data/ext/herb/extension_helpers.c +1 -0
  6. data/ext/herb/nodes.c +110 -0
  7. data/lib/herb/3.0/herb.so +0 -0
  8. data/lib/herb/3.1/herb.so +0 -0
  9. data/lib/herb/3.2/herb.so +0 -0
  10. data/lib/herb/3.3/herb.so +0 -0
  11. data/lib/herb/3.4/herb.so +0 -0
  12. data/lib/herb/4.0/herb.so +0 -0
  13. data/lib/herb/ast/nodes.rb +393 -17
  14. data/lib/herb/engine/compiler.rb +17 -41
  15. data/lib/herb/engine.rb +76 -26
  16. data/lib/herb/errors.rb +245 -0
  17. data/lib/herb/parser_options.rb +6 -1
  18. data/lib/herb/prism_inspect.rb +5 -1
  19. data/lib/herb/version.rb +1 -1
  20. data/lib/herb/visitor.rb +10 -0
  21. data/sig/herb/ast/nodes.rbs +132 -0
  22. data/sig/herb/engine/compiler.rbs +4 -2
  23. data/sig/herb/engine.rbs +8 -0
  24. data/sig/herb/errors.rbs +114 -0
  25. data/sig/herb/parser_options.rbs +4 -0
  26. data/sig/herb/visitor.rbs +6 -0
  27. data/sig/rubyvm.rbs +5 -0
  28. data/sig/serialized_ast_errors.rbs +28 -0
  29. data/sig/serialized_ast_nodes.rbs +31 -0
  30. data/src/analyze/action_view/attribute_extraction_helpers.c +23 -1
  31. data/src/analyze/action_view/content_tag.c +19 -11
  32. data/src/analyze/action_view/javascript_include_tag.c +92 -0
  33. data/src/analyze/action_view/javascript_tag.c +55 -0
  34. data/src/analyze/action_view/link_to.c +25 -1
  35. data/src/analyze/action_view/registry.c +29 -2
  36. data/src/analyze/action_view/tag.c +14 -8
  37. data/src/analyze/action_view/tag_helper_node_builders.c +16 -3
  38. data/src/analyze/action_view/tag_helpers.c +332 -12
  39. data/src/analyze/analyze.c +3 -0
  40. data/src/analyze/prism_annotate.c +4 -2
  41. data/src/analyze/render_nodes.c +761 -0
  42. data/src/analyze/transform.c +7 -0
  43. data/src/ast_nodes.c +97 -0
  44. data/src/ast_pretty_print.c +74 -0
  45. data/src/errors.c +379 -0
  46. data/src/html_util.c +50 -0
  47. data/src/include/analyze/action_view/tag_helper_handler.h +2 -0
  48. data/src/include/analyze/render_nodes.h +11 -0
  49. data/src/include/ast_nodes.h +37 -0
  50. data/src/include/errors.h +58 -0
  51. data/src/include/html_util.h +1 -0
  52. data/src/include/parser.h +1 -0
  53. data/src/include/version.h +1 -1
  54. data/src/parser.c +1 -0
  55. data/src/parser_match_tags.c +20 -0
  56. data/src/util/hb_arena.c +3 -7
  57. data/src/visitor.c +20 -0
  58. data/templates/lib/herb/ast/nodes.rb.erb +8 -2
  59. data/templates/rust/src/ast/nodes.rs.erb +1 -1
  60. data/templates/rust/src/nodes.rs.erb +1 -1
  61. metadata +6 -1
@@ -5,6 +5,7 @@
5
5
  #include "../../include/analyze/analyze.h"
6
6
  #include "../../include/ast_nodes.h"
7
7
  #include "../../include/html_util.h"
8
+ #include "../../include/parser_helpers.h"
8
9
  #include "../../include/position.h"
9
10
  #include "../../include/util/hb_allocator.h"
10
11
  #include "../../include/util/hb_array.h"
@@ -22,6 +23,10 @@ extern char* wrap_in_url_for(const char*, size_t, hb_allocator_T*);
22
23
  extern char* extract_link_to_href(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
23
24
  extern bool detect_turbo_frame_tag(pm_call_node_t*, pm_parser_t*);
24
25
  extern char* extract_turbo_frame_tag_id(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
26
+ extern bool detect_javascript_include_tag(pm_call_node_t*, pm_parser_t*);
27
+ extern char* extract_javascript_include_tag_src(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
28
+ extern char* wrap_in_javascript_path(const char*, size_t, hb_allocator_T*);
29
+ extern bool javascript_include_tag_source_is_url(const char*, size_t);
25
30
 
26
31
  typedef struct {
27
32
  pm_parser_t parser;
@@ -262,13 +267,29 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
262
267
  if (parse_context->info->call_node && handler->extract_content) {
263
268
  helper_content = handler->extract_content(parse_context->info->call_node, &parse_context->parser, allocator);
264
269
 
265
- if (helper_content && parse_context->info->call_node->arguments) {
266
- if (strcmp(handler->name, "content_tag") == 0 && parse_context->info->call_node->arguments->arguments.size >= 2) {
267
- content_is_ruby_expression =
268
- (parse_context->info->call_node->arguments->arguments.nodes[1]->type != PM_STRING_NODE);
269
- } else if (parse_context->info->call_node->arguments->arguments.size >= 1) {
270
- content_is_ruby_expression =
271
- (parse_context->info->call_node->arguments->arguments.nodes[0]->type != PM_STRING_NODE);
270
+ if (helper_content) {
271
+ pm_call_node_t* call = parse_context->info->call_node;
272
+
273
+ if (call->arguments) {
274
+ if (strcmp(handler->name, "content_tag") == 0 && call->arguments->arguments.size >= 2
275
+ && call->arguments->arguments.nodes[1]->type != PM_KEYWORD_HASH_NODE) {
276
+ content_is_ruby_expression = (call->arguments->arguments.nodes[1]->type != PM_STRING_NODE);
277
+ } else if (strcmp(handler->name, "content_tag") != 0 && call->arguments->arguments.size >= 1
278
+ && call->arguments->arguments.nodes[0]->type != PM_KEYWORD_HASH_NODE) {
279
+ content_is_ruby_expression = (call->arguments->arguments.nodes[0]->type != PM_STRING_NODE);
280
+ }
281
+ }
282
+
283
+ if (!content_is_ruby_expression && call->block && call->block->type == PM_BLOCK_NODE) {
284
+ pm_block_node_t* block_node = (pm_block_node_t*) call->block;
285
+
286
+ if (block_node->body && block_node->body->type == PM_STATEMENTS_NODE) {
287
+ pm_statements_node_t* statements = (pm_statements_node_t*) block_node->body;
288
+
289
+ if (statements->body.size == 1) {
290
+ content_is_ruby_expression = (statements->body.nodes[0]->type != PM_STRING_NODE);
291
+ }
292
+ }
272
293
  }
273
294
  }
274
295
  }
@@ -294,6 +315,47 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
294
315
  }
295
316
  }
296
317
 
318
+ if (detect_javascript_include_tag(parse_context->info->call_node, &parse_context->parser)
319
+ && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
320
+ char* source_value =
321
+ extract_javascript_include_tag_src(parse_context->info->call_node, &parse_context->parser, allocator);
322
+
323
+ if (source_value) {
324
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
325
+
326
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
327
+ position_T source_start, source_end;
328
+ prism_node_location_to_positions(&first_argument->location, parse_context, &source_start, &source_end);
329
+ bool source_is_string = (first_argument->type == PM_STRING_NODE);
330
+
331
+ size_t source_length = strlen(source_value);
332
+ bool is_url = javascript_include_tag_source_is_url(source_value, source_length);
333
+
334
+ char* source_attribute_value = source_value;
335
+ if (source_is_string && !is_url) {
336
+ size_t quoted_length = source_length + 2;
337
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
338
+ quoted_source[0] = '"';
339
+ memcpy(quoted_source + 1, source_value, source_length);
340
+ quoted_source[quoted_length - 1] = '"';
341
+ quoted_source[quoted_length] = '\0';
342
+
343
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, allocator);
344
+ hb_allocator_dealloc(allocator, quoted_source);
345
+ }
346
+
347
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
348
+ is_url
349
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
350
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
351
+
352
+ if (source_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) source_attribute, allocator); }
353
+
354
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
355
+ hb_allocator_dealloc(allocator, source_value);
356
+ }
357
+ }
358
+
297
359
  token_T* tag_name_token =
298
360
  tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
299
361
 
@@ -356,6 +418,166 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
356
418
  return (AST_NODE_T*) element;
357
419
  }
358
420
 
421
+ static size_t count_javascript_include_tag_sources(pm_call_node_t* call_node) {
422
+ if (!call_node || !call_node->arguments) { return 0; }
423
+
424
+ size_t count = 0;
425
+ pm_arguments_node_t* arguments = call_node->arguments;
426
+
427
+ for (size_t i = 0; i < arguments->arguments.size; i++) {
428
+ pm_node_t* arg = arguments->arguments.nodes[i];
429
+ if (arg->type == PM_KEYWORD_HASH_NODE) { break; }
430
+ count++;
431
+ }
432
+
433
+ return count;
434
+ }
435
+
436
+ static AST_NODE_T* create_javascript_include_tag_element(
437
+ AST_ERB_CONTENT_NODE_T* erb_node,
438
+ tag_helper_parse_context_T* parse_context,
439
+ pm_node_t* source_argument,
440
+ hb_array_T* shared_attributes,
441
+ hb_allocator_T* allocator
442
+ ) {
443
+ position_T tag_name_start, tag_name_end;
444
+ calculate_tag_name_positions(
445
+ parse_context,
446
+ erb_node->base.location.start,
447
+ erb_node->base.location.end,
448
+ &tag_name_start,
449
+ &tag_name_end
450
+ );
451
+
452
+ token_T* tag_name_token = create_synthetic_token(allocator, "script", TOKEN_IDENTIFIER, tag_name_start, tag_name_end);
453
+
454
+ char* source_value = NULL;
455
+ bool source_is_string = (source_argument->type == PM_STRING_NODE);
456
+
457
+ if (source_is_string) {
458
+ pm_string_node_t* string_node = (pm_string_node_t*) source_argument;
459
+ size_t length = pm_string_length(&string_node->unescaped);
460
+ source_value = hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
461
+ } else {
462
+ size_t source_length = source_argument->location.end - source_argument->location.start;
463
+ source_value = hb_allocator_strndup(allocator, (const char*) source_argument->location.start, source_length);
464
+ }
465
+
466
+ position_T source_start, source_end;
467
+ prism_node_location_to_positions(&source_argument->location, parse_context, &source_start, &source_end);
468
+
469
+ size_t source_length = strlen(source_value);
470
+ bool is_url = javascript_include_tag_source_is_url(source_value, source_length);
471
+
472
+ char* source_attribute_value = source_value;
473
+ if (source_is_string && !is_url) {
474
+ size_t quoted_length = source_length + 2;
475
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
476
+ quoted_source[0] = '"';
477
+ memcpy(quoted_source + 1, source_value, source_length);
478
+ quoted_source[quoted_length - 1] = '"';
479
+ quoted_source[quoted_length] = '\0';
480
+
481
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, allocator);
482
+ hb_allocator_dealloc(allocator, quoted_source);
483
+ }
484
+
485
+ hb_array_T* attributes = hb_array_init(hb_array_size(shared_attributes) + 1, allocator);
486
+
487
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
488
+ is_url
489
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
490
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
491
+ if (source_attribute) { hb_array_append(attributes, source_attribute); }
492
+
493
+ for (size_t i = 0; i < hb_array_size(shared_attributes); i++) {
494
+ hb_array_append(attributes, hb_array_get(shared_attributes, i));
495
+ }
496
+
497
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
498
+ hb_allocator_dealloc(allocator, source_value);
499
+
500
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
501
+ erb_node->tag_opening,
502
+ erb_node->content,
503
+ erb_node->tag_closing,
504
+ tag_name_token,
505
+ attributes,
506
+ erb_node->base.location.start,
507
+ erb_node->base.location.end,
508
+ hb_array_init(0, allocator),
509
+ allocator
510
+ );
511
+
512
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
513
+ tag_name_token,
514
+ erb_node->base.location.end,
515
+ erb_node->base.location.end,
516
+ hb_array_init(0, allocator),
517
+ allocator
518
+ );
519
+
520
+ return (AST_NODE_T*) ast_html_element_node_init(
521
+ (AST_NODE_T*) open_tag_node,
522
+ tag_name_token,
523
+ hb_array_init(0, allocator),
524
+ (AST_NODE_T*) virtual_close,
525
+ false,
526
+ parse_context->matched_handler->source,
527
+ erb_node->base.location.start,
528
+ erb_node->base.location.end,
529
+ hb_array_init(0, allocator),
530
+ allocator
531
+ );
532
+ }
533
+
534
+ static hb_array_T* transform_javascript_include_tag_multi_source(
535
+ AST_ERB_CONTENT_NODE_T* erb_node,
536
+ analyze_ruby_context_T* context,
537
+ tag_helper_parse_context_T* parse_context
538
+ ) {
539
+ hb_allocator_T* allocator = context->allocator;
540
+ pm_call_node_t* call_node = parse_context->info->call_node;
541
+ size_t source_count = count_javascript_include_tag_sources(call_node);
542
+
543
+ if (source_count == 0) { return NULL; }
544
+
545
+ hb_array_T* shared_attributes = extract_html_attributes_from_call_node(
546
+ call_node,
547
+ parse_context->prism_source,
548
+ parse_context->original_source,
549
+ parse_context->erb_content_offset,
550
+ allocator
551
+ );
552
+ if (!shared_attributes) { shared_attributes = hb_array_init(0, allocator); }
553
+
554
+ hb_array_T* elements = hb_array_init(source_count * 2, allocator);
555
+
556
+ for (size_t i = 0; i < source_count; i++) {
557
+ pm_node_t* source_arg = call_node->arguments->arguments.nodes[i];
558
+ AST_NODE_T* element =
559
+ create_javascript_include_tag_element(erb_node, parse_context, source_arg, shared_attributes, allocator);
560
+
561
+ if (element) {
562
+ if (hb_array_size(elements) > 0) {
563
+ position_T position = erb_node->base.location.start;
564
+ AST_HTML_TEXT_NODE_T* newline = ast_html_text_node_init(
565
+ hb_string_from_c_string("\n"),
566
+ position,
567
+ position,
568
+ hb_array_init(0, allocator),
569
+ allocator
570
+ );
571
+ if (newline) { hb_array_append(elements, (AST_NODE_T*) newline); }
572
+ }
573
+
574
+ hb_array_append(elements, element);
575
+ }
576
+ }
577
+
578
+ return elements;
579
+ }
580
+
359
581
  static AST_NODE_T* transform_erb_block_to_tag_helper(
360
582
  AST_ERB_BLOCK_NODE_T* block_node,
361
583
  analyze_ruby_context_T* context,
@@ -462,6 +684,27 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
462
684
  hb_array_T* body = block_node->body ? block_node->body : hb_array_init(0, allocator);
463
685
  AST_NODE_T* close_tag = (AST_NODE_T*) block_node->end_node;
464
686
 
687
+ if (tag_name && parser_is_foreign_content_tag(hb_string_from_c_string(tag_name)) && context->source
688
+ && block_node->body && hb_array_size(block_node->body) > 0) {
689
+ size_t start_offset = block_node->tag_closing->range.to;
690
+ size_t end_offset = block_node->end_node->tag_opening->range.from;
691
+
692
+ if (end_offset > start_offset) {
693
+ position_T body_start = block_node->tag_closing->location.end;
694
+ position_T body_end = block_node->end_node->tag_opening->location.start;
695
+
696
+ size_t content_length = end_offset - start_offset;
697
+ char* raw_copy = hb_allocator_strndup(allocator, context->source + start_offset, content_length);
698
+ hb_string_T raw_content = { .data = raw_copy, .length = content_length };
699
+
700
+ AST_LITERAL_NODE_T* literal_node =
701
+ ast_literal_node_init(raw_content, body_start, body_end, hb_array_init(0, allocator), allocator);
702
+
703
+ body = hb_array_init(1, allocator);
704
+ hb_array_append(body, literal_node);
705
+ }
706
+ }
707
+
465
708
  AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
466
709
  (AST_NODE_T*) open_tag_node,
467
710
  tag_name_token,
@@ -492,8 +735,12 @@ static AST_NODE_T* transform_link_to_helper(
492
735
 
493
736
  hb_array_T* attributes = NULL;
494
737
  pm_arguments_node_t* link_arguments = info->call_node->arguments;
495
- bool keyword_hash_is_url = link_arguments && link_arguments->arguments.size == 2
496
- && link_arguments->arguments.nodes[1]->type == PM_KEYWORD_HASH_NODE;
738
+ bool has_inline_block = info->call_node->block && info->call_node->block->type == PM_BLOCK_NODE;
739
+
740
+ bool second_arg_is_hash = link_arguments && link_arguments->arguments.size == 2
741
+ && (link_arguments->arguments.nodes[1]->type == PM_KEYWORD_HASH_NODE
742
+ || link_arguments->arguments.nodes[1]->type == PM_HASH_NODE);
743
+ bool keyword_hash_is_url = !has_inline_block && second_arg_is_hash;
497
744
 
498
745
  if (!keyword_hash_is_url) {
499
746
  attributes = extract_html_attributes_from_call_node(
@@ -507,6 +754,38 @@ static AST_NODE_T* transform_link_to_helper(
507
754
 
508
755
  if (!attributes) { attributes = hb_array_init(4, allocator); }
509
756
 
757
+ if (has_inline_block && link_arguments && link_arguments->arguments.size >= 2) {
758
+ pm_node_t* second_arg = link_arguments->arguments.nodes[1];
759
+
760
+ if (second_arg->type != PM_KEYWORD_HASH_NODE && second_arg->type != PM_HASH_NODE
761
+ && second_arg->type != PM_STRING_NODE && second_arg->type != PM_SYMBOL_NODE) {
762
+ size_t source_length = second_arg->location.end - second_arg->location.start;
763
+ char* content = hb_allocator_strndup(allocator, (const char*) second_arg->location.start, source_length);
764
+
765
+ if (content) {
766
+ position_T position = prism_location_to_position_with_offset(
767
+ &second_arg->location,
768
+ parse_context->original_source,
769
+ parse_context->erb_content_offset,
770
+ parse_context->prism_source
771
+ );
772
+
773
+ AST_RUBY_HTML_ATTRIBUTES_SPLAT_NODE_T* splat_node = ast_ruby_html_attributes_splat_node_init(
774
+ hb_string_from_c_string(content),
775
+ HB_STRING_EMPTY,
776
+ position,
777
+ position,
778
+ hb_array_init(0, allocator),
779
+ allocator
780
+ );
781
+
782
+ if (splat_node) { hb_array_append(attributes, (AST_NODE_T*) splat_node); }
783
+
784
+ hb_allocator_dealloc(allocator, content);
785
+ }
786
+ }
787
+ }
788
+
510
789
  // `method:` implies `rel="nofollow"`
511
790
  bool has_data_method = false;
512
791
  hb_string_T data_method_string = hb_string("data-method");
@@ -549,7 +828,12 @@ static AST_NODE_T* transform_link_to_helper(
549
828
  pm_arguments_node_t* arguments = info->call_node->arguments;
550
829
  pm_node_t* href_argument = NULL;
551
830
 
552
- if (arguments->arguments.size >= 2) {
831
+ if (has_inline_block) {
832
+ if (arguments->arguments.size >= 1) {
833
+ href_argument = arguments->arguments.nodes[0];
834
+ href_is_ruby_expression = (href_argument->type != PM_STRING_NODE);
835
+ }
836
+ } else if (arguments->arguments.size >= 2) {
553
837
  href_argument = arguments->arguments.nodes[1];
554
838
  href_is_ruby_expression = (href_argument->type != PM_STRING_NODE);
555
839
  } else if (arguments->arguments.size == 1) {
@@ -602,7 +886,17 @@ static AST_NODE_T* transform_link_to_helper(
602
886
  if (info->content) {
603
887
  bool content_is_ruby_expression = false;
604
888
 
605
- if (info->call_node && info->call_node->arguments && info->call_node->arguments->arguments.size >= 1) {
889
+ if (has_inline_block && info->call_node->block && info->call_node->block->type == PM_BLOCK_NODE) {
890
+ pm_block_node_t* block_node = (pm_block_node_t*) info->call_node->block;
891
+
892
+ if (block_node->body && block_node->body->type == PM_STATEMENTS_NODE) {
893
+ pm_statements_node_t* statements = (pm_statements_node_t*) block_node->body;
894
+
895
+ if (statements->body.size == 1) {
896
+ content_is_ruby_expression = (statements->body.nodes[0]->type != PM_STRING_NODE);
897
+ }
898
+ }
899
+ } else if (info->call_node && info->call_node->arguments && info->call_node->arguments->arguments.size >= 1) {
606
900
  pm_node_t* first_argument = info->call_node->arguments->arguments.nodes[0];
607
901
  content_is_ruby_expression = (first_argument->type != PM_STRING_NODE);
608
902
  }
@@ -720,7 +1014,33 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
720
1014
  parse_tag_helper_content(erb_string, context->source, erb_content_offset, context->allocator);
721
1015
 
722
1016
  if (parse_context) {
723
- if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
1017
+ if (strcmp(parse_context->matched_handler->name, "javascript_include_tag") == 0
1018
+ && parse_context->info->call_node
1019
+ && count_javascript_include_tag_sources(parse_context->info->call_node) > 1) {
1020
+ hb_array_T* multi = transform_javascript_include_tag_multi_source(erb_node, context, parse_context);
1021
+
1022
+ if (multi && hb_array_size(multi) > 0) {
1023
+ size_t old_size = hb_array_size(array);
1024
+ size_t multi_size = hb_array_size(multi);
1025
+ hb_array_T* new_array = hb_array_init(old_size - 1 + multi_size, context->allocator);
1026
+
1027
+ for (size_t j = 0; j < old_size; j++) {
1028
+ if (j == i) {
1029
+ for (size_t k = 0; k < multi_size; k++) {
1030
+ hb_array_append(new_array, hb_array_get(multi, k));
1031
+ }
1032
+ } else {
1033
+ hb_array_append(new_array, hb_array_get(array, j));
1034
+ }
1035
+ }
1036
+
1037
+ array->items = new_array->items;
1038
+ array->size = new_array->size;
1039
+ array->capacity = new_array->capacity;
1040
+
1041
+ i += multi_size - 1;
1042
+ }
1043
+ } else if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
724
1044
  replacement = transform_link_to_helper(erb_node, context, parse_context);
725
1045
  } else {
726
1046
  replacement = transform_tag_helper_with_attributes(erb_node, context, parse_context);
@@ -8,6 +8,7 @@
8
8
  #include "../include/analyze/control_type.h"
9
9
  #include "../include/analyze/helpers.h"
10
10
  #include "../include/analyze/invalid_structures.h"
11
+ #include "../include/analyze/render_nodes.h"
11
12
  #include "../include/ast_node.h"
12
13
  #include "../include/ast_nodes.h"
13
14
  #include "../include/errors.h"
@@ -859,6 +860,8 @@ void herb_analyze_parse_tree(
859
860
 
860
861
  herb_visit_node((AST_NODE_T*) document, transform_erb_nodes, &context);
861
862
 
863
+ if (options && options->render_nodes) { herb_visit_node((AST_NODE_T*) document, transform_render_nodes, &context); }
864
+
862
865
  if (options && options->action_view_helpers) {
863
866
  herb_visit_node((AST_NODE_T*) document, transform_tag_helper_nodes, &context);
864
867
  }
@@ -168,6 +168,7 @@ static void collect_prism_nodes(pm_node_t* node, hb_narray_T* list) {
168
168
  static token_T* get_content_token(const AST_NODE_T* node) {
169
169
  switch (node->type) {
170
170
  case AST_ERB_CONTENT_NODE: return ((AST_ERB_CONTENT_NODE_T*) node)->content;
171
+ case AST_ERB_RENDER_NODE: return ((AST_ERB_RENDER_NODE_T*) node)->content;
171
172
  case AST_ERB_IF_NODE: return ((AST_ERB_IF_NODE_T*) node)->content;
172
173
  case AST_ERB_BLOCK_NODE: return ((AST_ERB_BLOCK_NODE_T*) node)->content;
173
174
  case AST_ERB_CASE_NODE: return ((AST_ERB_CASE_NODE_T*) node)->content;
@@ -184,6 +185,7 @@ static token_T* get_content_token(const AST_NODE_T* node) {
184
185
  static void set_prism_node(AST_NODE_T* node, herb_prism_node_T prism_ref) {
185
186
  switch (node->type) {
186
187
  case AST_ERB_CONTENT_NODE: ((AST_ERB_CONTENT_NODE_T*) node)->prism_node = prism_ref; break;
188
+ case AST_ERB_RENDER_NODE: ((AST_ERB_RENDER_NODE_T*) node)->prism_node = prism_ref; break;
187
189
  case AST_ERB_IF_NODE: ((AST_ERB_IF_NODE_T*) node)->prism_node = prism_ref; break;
188
190
  case AST_ERB_BLOCK_NODE: ((AST_ERB_BLOCK_NODE_T*) node)->prism_node = prism_ref; break;
189
191
  case AST_ERB_CASE_NODE: ((AST_ERB_CASE_NODE_T*) node)->prism_node = prism_ref; break;
@@ -232,7 +234,7 @@ static bool annotate_visitor(const AST_NODE_T* node, void* data) {
232
234
  pm_parser_t* parser;
233
235
  hb_narray_T* node_list;
234
236
 
235
- if (node->type == AST_ERB_CONTENT_NODE || context->prism_nodes_deep) {
237
+ if (node->type == AST_ERB_CONTENT_NODE || node->type == AST_ERB_RENDER_NODE || context->prism_nodes_deep) {
236
238
  parser = context->parser;
237
239
  node_list = context->node_list;
238
240
  } else {
@@ -248,7 +250,7 @@ static bool annotate_visitor(const AST_NODE_T* node, void* data) {
248
250
  }
249
251
 
250
252
  static bool collect_content_ranges_visitor(const AST_NODE_T* node, void* data) {
251
- if (node->type != AST_ERB_CONTENT_NODE) { return true; }
253
+ if (node->type != AST_ERB_CONTENT_NODE && node->type != AST_ERB_RENDER_NODE) { return true; }
252
254
 
253
255
  hb_narray_T* ranges = (hb_narray_T*) data;
254
256
  token_T* content = get_content_token(node);