herb 0.9.2-arm-linux-gnu → 0.9.4-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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/config.yml +125 -0
  4. data/ext/herb/error_helpers.c +172 -2
  5. data/ext/herb/extconf.rb +6 -0
  6. data/ext/herb/extension.c +16 -2
  7. data/ext/herb/extension_helpers.c +6 -5
  8. data/ext/herb/extension_helpers.h +4 -4
  9. data/ext/herb/nodes.c +89 -3
  10. data/lib/herb/3.0/herb.so +0 -0
  11. data/lib/herb/3.1/herb.so +0 -0
  12. data/lib/herb/3.2/herb.so +0 -0
  13. data/lib/herb/3.3/herb.so +0 -0
  14. data/lib/herb/3.4/herb.so +0 -0
  15. data/lib/herb/4.0/herb.so +0 -0
  16. data/lib/herb/ast/erb_content_node.rb +32 -0
  17. data/lib/herb/ast/nodes.rb +244 -3
  18. data/lib/herb/cli.rb +12 -2
  19. data/lib/herb/engine/compiler.rb +166 -75
  20. data/lib/herb/engine/validators/security_validator.rb +40 -0
  21. data/lib/herb/engine.rb +3 -0
  22. data/lib/herb/errors.rb +268 -0
  23. data/lib/herb/parser_options.rb +7 -2
  24. data/lib/herb/project.rb +58 -17
  25. data/lib/herb/version.rb +1 -1
  26. data/lib/herb/visitor.rb +82 -0
  27. data/lib/herb.rb +1 -0
  28. data/sig/herb/ast/erb_content_node.rbs +13 -0
  29. data/sig/herb/ast/nodes.rbs +98 -2
  30. data/sig/herb/engine/compiler.rbs +31 -2
  31. data/sig/herb/engine/validators/security_validator.rbs +4 -0
  32. data/sig/herb/engine.rbs +3 -0
  33. data/sig/herb/errors.rbs +122 -0
  34. data/sig/herb/parser_options.rbs +6 -2
  35. data/sig/herb/visitor.rbs +12 -0
  36. data/sig/serialized_ast_errors.rbs +29 -0
  37. data/sig/serialized_ast_nodes.rbs +19 -0
  38. data/src/analyze/action_view/attribute_extraction_helpers.c +420 -91
  39. data/src/analyze/action_view/image_tag.c +87 -0
  40. data/src/analyze/action_view/javascript_include_tag.c +22 -12
  41. data/src/analyze/action_view/registry.c +6 -3
  42. data/src/analyze/action_view/tag.c +19 -2
  43. data/src/analyze/action_view/tag_helper_node_builders.c +105 -36
  44. data/src/analyze/action_view/tag_helpers.c +792 -44
  45. data/src/analyze/analyze.c +167 -13
  46. data/src/analyze/{helpers.c → analyze_helpers.c} +1 -1
  47. data/src/analyze/analyzed_ruby.c +1 -1
  48. data/src/analyze/builders.c +11 -8
  49. data/src/analyze/conditional_elements.c +6 -7
  50. data/src/analyze/conditional_open_tags.c +6 -7
  51. data/src/analyze/control_type.c +4 -2
  52. data/src/analyze/invalid_structures.c +5 -5
  53. data/src/analyze/missing_end.c +2 -2
  54. data/src/analyze/parse_errors.c +47 -6
  55. data/src/analyze/prism_annotate.c +7 -7
  56. data/src/analyze/render_nodes.c +6 -26
  57. data/src/analyze/strict_locals.c +651 -0
  58. data/src/analyze/transform.c +7 -0
  59. data/src/{ast_node.c → ast/ast_node.c} +8 -8
  60. data/src/{ast_nodes.c → ast/ast_nodes.c} +82 -11
  61. data/src/{ast_pretty_print.c → ast/ast_pretty_print.c} +113 -9
  62. data/src/{pretty_print.c → ast/pretty_print.c} +9 -9
  63. data/src/errors.c +398 -8
  64. data/src/extract.c +5 -5
  65. data/src/herb.c +15 -5
  66. data/src/include/analyze/action_view/attribute_extraction_helpers.h +3 -1
  67. data/src/include/analyze/action_view/tag_helper_handler.h +3 -3
  68. data/src/include/analyze/action_view/tag_helper_node_builders.h +34 -5
  69. data/src/include/analyze/action_view/tag_helpers.h +4 -3
  70. data/src/include/analyze/analyze.h +12 -5
  71. data/src/include/analyze/analyzed_ruby.h +2 -2
  72. data/src/include/analyze/builders.h +4 -4
  73. data/src/include/analyze/conditional_elements.h +2 -2
  74. data/src/include/analyze/conditional_open_tags.h +2 -2
  75. data/src/include/analyze/control_type.h +1 -1
  76. data/src/include/analyze/helpers.h +2 -2
  77. data/src/include/analyze/invalid_structures.h +1 -1
  78. data/src/include/analyze/prism_annotate.h +2 -2
  79. data/src/include/analyze/render_nodes.h +1 -1
  80. data/src/include/analyze/strict_locals.h +11 -0
  81. data/src/include/{ast_node.h → ast/ast_node.h} +4 -4
  82. data/src/include/{ast_nodes.h → ast/ast_nodes.h} +38 -14
  83. data/src/include/{ast_pretty_print.h → ast/ast_pretty_print.h} +3 -3
  84. data/src/include/{pretty_print.h → ast/pretty_print.h} +4 -4
  85. data/src/include/errors.h +65 -7
  86. data/src/include/extract.h +2 -2
  87. data/src/include/herb.h +5 -5
  88. data/src/include/{lex_helpers.h → lexer/lex_helpers.h} +5 -5
  89. data/src/include/{lexer.h → lexer/lexer.h} +1 -1
  90. data/src/include/{lexer_peek_helpers.h → lexer/lexer_peek_helpers.h} +2 -2
  91. data/src/include/{lexer_struct.h → lexer/lexer_struct.h} +2 -2
  92. data/src/include/{token.h → lexer/token.h} +3 -3
  93. data/src/include/{token_matchers.h → lexer/token_matchers.h} +1 -1
  94. data/src/include/{token_struct.h → lexer/token_struct.h} +3 -3
  95. data/src/include/{util → lib}/hb_foreach.h +1 -1
  96. data/src/include/{util → lib}/hb_string.h +5 -1
  97. data/src/include/{location.h → location/location.h} +1 -1
  98. data/src/include/parser/dot_notation.h +12 -0
  99. data/src/include/{parser.h → parser/parser.h} +11 -4
  100. data/src/include/{parser_helpers.h → parser/parser_helpers.h} +6 -6
  101. data/src/include/{prism_context.h → prism/prism_context.h} +2 -2
  102. data/src/include/{prism_helpers.h → prism/prism_helpers.h} +6 -6
  103. data/src/include/{html_util.h → util/html_util.h} +1 -1
  104. data/src/include/util/ruby_util.h +9 -0
  105. data/src/include/{utf8.h → util/utf8.h} +1 -1
  106. data/src/include/{util.h → util/util.h} +1 -1
  107. data/src/include/version.h +1 -1
  108. data/src/include/visitor.h +3 -3
  109. data/src/{lexer_peek_helpers.c → lexer/lexer_peek_helpers.c} +3 -3
  110. data/src/{token.c → lexer/token.c} +8 -8
  111. data/src/{token_matchers.c → lexer/token_matchers.c} +2 -2
  112. data/src/lexer.c +6 -6
  113. data/src/{util → lib}/hb_allocator.c +2 -2
  114. data/src/{util → lib}/hb_arena.c +1 -1
  115. data/src/{util → lib}/hb_arena_debug.c +2 -2
  116. data/src/{util → lib}/hb_array.c +2 -2
  117. data/src/{util → lib}/hb_buffer.c +2 -2
  118. data/src/{util → lib}/hb_narray.c +1 -1
  119. data/src/{util → lib}/hb_string.c +2 -2
  120. data/src/{location.c → location/location.c} +2 -2
  121. data/src/{position.c → location/position.c} +2 -2
  122. data/src/{range.c → location/range.c} +1 -1
  123. data/src/main.c +11 -11
  124. data/src/parser/dot_notation.c +100 -0
  125. data/src/{parser_match_tags.c → parser/match_tags.c} +34 -5
  126. data/src/{parser_helpers.c → parser/parser_helpers.c} +10 -10
  127. data/src/parser.c +68 -32
  128. data/src/{prism_helpers.c → prism/prism_helpers.c} +7 -7
  129. data/src/{ruby_parser.c → prism/ruby_parser.c} +1 -1
  130. data/src/{html_util.c → util/html_util.c} +4 -4
  131. data/src/{io.c → util/io.c} +3 -3
  132. data/src/util/ruby_util.c +42 -0
  133. data/src/{utf8.c → util/utf8.c} +2 -2
  134. data/src/{util.c → util/util.c} +4 -4
  135. data/src/visitor.c +35 -3
  136. data/templates/ext/herb/error_helpers.c.erb +2 -2
  137. data/templates/ext/herb/nodes.c.erb +1 -1
  138. data/templates/java/error_helpers.c.erb +1 -1
  139. data/templates/java/error_helpers.h.erb +2 -2
  140. data/templates/java/nodes.c.erb +4 -4
  141. data/templates/java/nodes.h.erb +1 -1
  142. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +4 -4
  143. data/templates/javascript/packages/node/extension/nodes.cpp.erb +4 -4
  144. data/templates/lib/herb/visitor.rb.erb +14 -0
  145. data/templates/src/analyze/missing_end.c.erb +2 -2
  146. data/templates/src/{ast_nodes.c.erb → ast/ast_nodes.c.erb} +9 -9
  147. data/templates/src/{ast_pretty_print.c.erb → ast/ast_pretty_print.c.erb} +8 -8
  148. data/templates/src/errors.c.erb +8 -8
  149. data/templates/src/include/{ast_nodes.h.erb → ast/ast_nodes.h.erb} +11 -12
  150. data/templates/src/include/{ast_pretty_print.h.erb → ast/ast_pretty_print.h.erb} +2 -2
  151. data/templates/src/include/errors.h.erb +7 -7
  152. data/templates/src/{parser_match_tags.c.erb → parser/match_tags.c.erb} +4 -4
  153. data/templates/src/visitor.c.erb +3 -3
  154. data/templates/wasm/error_helpers.cpp.erb +4 -4
  155. data/templates/wasm/nodes.cpp.erb +5 -5
  156. metadata +76 -68
  157. data/src/include/element_source.h +0 -10
  158. /data/src/include/{util → lib}/hb_allocator.h +0 -0
  159. /data/src/include/{util → lib}/hb_arena.h +0 -0
  160. /data/src/include/{util → lib}/hb_arena_debug.h +0 -0
  161. /data/src/include/{util → lib}/hb_array.h +0 -0
  162. /data/src/include/{util → lib}/hb_buffer.h +0 -0
  163. /data/src/include/{util → lib}/hb_narray.h +0 -0
  164. /data/src/include/{util → lib}/string.h +0 -0
  165. /data/src/include/{position.h → location/position.h} +0 -0
  166. /data/src/include/{range.h → location/range.h} +0 -0
  167. /data/src/include/{herb_prism_node.h → prism/herb_prism_node.h} +0 -0
  168. /data/src/include/{prism_serialized.h → prism/prism_serialized.h} +0 -0
  169. /data/src/include/{ruby_parser.h → prism/ruby_parser.h} +0 -0
  170. /data/src/include/{io.h → util/io.h} +0 -0
  171. /data/templates/src/include/{util → lib}/hb_foreach.h.erb +0 -0
@@ -3,13 +3,17 @@
3
3
  #include "../../include/analyze/action_view/tag_helper_handler.h"
4
4
  #include "../../include/analyze/action_view/tag_helper_node_builders.h"
5
5
  #include "../../include/analyze/analyze.h"
6
- #include "../../include/ast_nodes.h"
7
- #include "../../include/html_util.h"
8
- #include "../../include/parser_helpers.h"
9
- #include "../../include/position.h"
10
- #include "../../include/util/hb_allocator.h"
11
- #include "../../include/util/hb_array.h"
12
- #include "../../include/util/hb_string.h"
6
+ #include "../../include/ast/ast_nodes.h"
7
+ #include "../../include/herb.h"
8
+ #include "../../include/lib/hb_allocator.h"
9
+ #include "../../include/lib/hb_array.h"
10
+ #include "../../include/lib/hb_buffer.h"
11
+ #include "../../include/lib/hb_string.h"
12
+ #include "../../include/lib/string.h"
13
+ #include "../../include/location/position.h"
14
+ #include "../../include/parser/parser_helpers.h"
15
+ #include "../../include/util/html_util.h"
16
+ #include "../../include/util/util.h"
13
17
  #include "../../include/visitor.h"
14
18
 
15
19
  #include <prism.h>
@@ -25,8 +29,12 @@ extern bool detect_turbo_frame_tag(pm_call_node_t*, pm_parser_t*);
25
29
  extern char* extract_turbo_frame_tag_id(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
26
30
  extern bool detect_javascript_include_tag(pm_call_node_t*, pm_parser_t*);
27
31
  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*);
32
+ extern char* wrap_in_javascript_path(const char*, size_t, const char*, hb_allocator_T*);
29
33
  extern bool javascript_include_tag_source_is_url(const char*, size_t);
34
+ extern bool detect_image_tag(pm_call_node_t*, pm_parser_t*);
35
+ extern char* extract_image_tag_src(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
36
+ extern char* wrap_in_image_path(const char*, size_t, const char*, hb_allocator_T*);
37
+ extern bool image_tag_source_is_url(const char*, size_t);
30
38
 
31
39
  typedef struct {
32
40
  pm_parser_t parser;
@@ -37,6 +45,8 @@ typedef struct {
37
45
  const tag_helper_handler_T* matched_handler;
38
46
  const char* original_source;
39
47
  size_t erb_content_offset;
48
+ char* condition_source;
49
+ char* condition_type;
40
50
  } tag_helper_parse_context_T;
41
51
 
42
52
  static tag_helper_parse_context_T* parse_tag_helper_content(
@@ -66,11 +76,15 @@ static tag_helper_parse_context_T* parse_tag_helper_content(
66
76
  }
67
77
 
68
78
  parse_context->info = tag_helper_info_init(allocator);
79
+ parse_context->condition_source = NULL;
80
+ parse_context->condition_type = NULL;
81
+
69
82
  tag_helper_search_data_T search = { .tag_helper_node = NULL,
70
83
  .source = parse_context->prism_source,
71
84
  .parser = &parse_context->parser,
72
85
  .info = parse_context->info,
73
- .found = false };
86
+ .found = false,
87
+ .postfix_conditional_node = NULL };
74
88
  pm_visit_node(parse_context->root, search_tag_helper_node, &search);
75
89
 
76
90
  if (!search.found) {
@@ -82,6 +96,22 @@ static tag_helper_parse_context_T* parse_tag_helper_content(
82
96
  return NULL;
83
97
  }
84
98
 
99
+ if (search.postfix_conditional_node) {
100
+ if (search.postfix_conditional_node->type == PM_IF_NODE) {
101
+ pm_if_node_t* if_node = (pm_if_node_t*) search.postfix_conditional_node;
102
+ size_t predicate_length = if_node->predicate->location.end - if_node->predicate->location.start;
103
+ parse_context->condition_source =
104
+ hb_allocator_strndup(allocator, (const char*) if_node->predicate->location.start, predicate_length);
105
+ parse_context->condition_type = hb_allocator_strdup(allocator, "if");
106
+ } else if (search.postfix_conditional_node->type == PM_UNLESS_NODE) {
107
+ pm_unless_node_t* unless_node = (pm_unless_node_t*) search.postfix_conditional_node;
108
+ size_t predicate_length = unless_node->predicate->location.end - unless_node->predicate->location.start;
109
+ parse_context->condition_source =
110
+ hb_allocator_strndup(allocator, (const char*) unless_node->predicate->location.start, predicate_length);
111
+ parse_context->condition_type = hb_allocator_strdup(allocator, "unless");
112
+ }
113
+ }
114
+
85
115
  parse_context->matched_handler = search.matched_handler;
86
116
  return parse_context;
87
117
  }
@@ -101,6 +131,92 @@ static void free_tag_helper_parse_context(tag_helper_parse_context_T* parse_cont
101
131
  }
102
132
  }
103
133
 
134
+ static AST_NODE_T* wrap_in_conditional_if_needed(
135
+ AST_NODE_T* element,
136
+ tag_helper_parse_context_T* parse_context,
137
+ hb_allocator_T* allocator
138
+ ) {
139
+ if (!element || !parse_context || !parse_context->condition_source || !parse_context->condition_type) {
140
+ return element;
141
+ }
142
+
143
+ position_T start = element->location.start;
144
+ position_T end = element->location.end;
145
+
146
+ hb_buffer_T content_buffer;
147
+ hb_buffer_init(&content_buffer, 64, allocator);
148
+ hb_buffer_append(&content_buffer, " ");
149
+ hb_buffer_append(&content_buffer, parse_context->condition_type);
150
+ hb_buffer_append(&content_buffer, " ");
151
+ hb_buffer_append(&content_buffer, parse_context->condition_source);
152
+ hb_buffer_append(&content_buffer, " ");
153
+ const char* content_string = hb_buffer_value(&content_buffer);
154
+
155
+ token_T* tag_opening = create_synthetic_token(allocator, "<%", TOKEN_ERB_START, start, start);
156
+ token_T* content_token = create_synthetic_token(allocator, content_string, TOKEN_ERB_CONTENT, start, end);
157
+ token_T* tag_closing = create_synthetic_token(allocator, "%>", TOKEN_ERB_END, end, end);
158
+
159
+ hb_array_T* statements = hb_array_init(1, allocator);
160
+ hb_array_append(statements, element);
161
+
162
+ token_T* end_opening = create_synthetic_token(allocator, "<%", TOKEN_ERB_START, end, end);
163
+ token_T* end_content = create_synthetic_token(allocator, " end ", TOKEN_ERB_CONTENT, end, end);
164
+ token_T* end_closing = create_synthetic_token(allocator, "%>", TOKEN_ERB_END, end, end);
165
+
166
+ AST_ERB_END_NODE_T* end_node =
167
+ ast_erb_end_node_init(end_opening, end_content, end_closing, end, end, hb_array_init(0, allocator), allocator);
168
+
169
+ herb_prism_node_T empty_prism_node = HERB_PRISM_NODE_EMPTY;
170
+
171
+ if (strcmp(parse_context->condition_type, "if") == 0) {
172
+ AST_ERB_IF_NODE_T* if_node = ast_erb_if_node_init(
173
+ tag_opening,
174
+ content_token,
175
+ tag_closing,
176
+ NULL,
177
+ empty_prism_node,
178
+ statements,
179
+ NULL,
180
+ end_node,
181
+ start,
182
+ end,
183
+ hb_array_init(0, allocator),
184
+ allocator
185
+ );
186
+
187
+ return (AST_NODE_T*) if_node;
188
+ } else {
189
+ AST_ERB_UNLESS_NODE_T* unless_node = ast_erb_unless_node_init(
190
+ tag_opening,
191
+ content_token,
192
+ tag_closing,
193
+ NULL,
194
+ empty_prism_node,
195
+ statements,
196
+ NULL,
197
+ end_node,
198
+ start,
199
+ end,
200
+ hb_array_init(0, allocator),
201
+ allocator
202
+ );
203
+
204
+ return (AST_NODE_T*) unless_node;
205
+ }
206
+ }
207
+
208
+ static bool is_postfix_if_node(const pm_node_t* node) {
209
+ if (node->type != PM_IF_NODE) { return false; }
210
+ pm_if_node_t* if_node = (pm_if_node_t*) node;
211
+ return if_node->if_keyword_loc.start != NULL && if_node->end_keyword_loc.start == NULL;
212
+ }
213
+
214
+ static bool is_postfix_unless_node(const pm_node_t* node) {
215
+ if (node->type != PM_UNLESS_NODE) { return false; }
216
+ pm_unless_node_t* unless_node = (pm_unless_node_t*) node;
217
+ return unless_node->keyword_loc.start != NULL && unless_node->end_keyword_loc.start == NULL;
218
+ }
219
+
104
220
  bool search_tag_helper_node(const pm_node_t* node, void* data) {
105
221
  tag_helper_search_data_T* search_data = (tag_helper_search_data_T*) data;
106
222
 
@@ -124,9 +240,21 @@ bool search_tag_helper_node(const pm_node_t* node, void* data) {
124
240
  search_data->info->has_block = handlers[i].supports_block();
125
241
  }
126
242
 
127
- return true;
243
+ return false;
128
244
  }
129
245
  }
246
+
247
+ return false;
248
+ }
249
+
250
+ if (is_postfix_if_node(node) || is_postfix_unless_node(node)) {
251
+ const pm_node_t* saved = search_data->postfix_conditional_node;
252
+ search_data->postfix_conditional_node = node;
253
+ pm_visit_child_nodes(node, search_tag_helper_node, search_data);
254
+
255
+ if (!search_data->found) { search_data->postfix_conditional_node = saved; }
256
+
257
+ return search_data->found;
130
258
  }
131
259
 
132
260
  pm_visit_child_nodes(node, search_tag_helper_node, search_data);
@@ -230,6 +358,98 @@ static void calculate_tag_name_positions(
230
358
  }
231
359
  }
232
360
 
361
+ static const char* JAVASCRIPT_INCLUDE_TAG_PATH_OPTIONS[] = { "protocol", "extname", "host", "skip_pipeline", NULL };
362
+ static const char* IMAGE_TAG_PATH_OPTIONS[] = { "skip_pipeline", NULL };
363
+
364
+ static char* extract_path_options_from_keyword_hash(
365
+ pm_call_node_t* call_node,
366
+ const char** option_names,
367
+ hb_allocator_T* allocator
368
+ ) {
369
+ if (!call_node || !call_node->arguments) { return NULL; }
370
+
371
+ pm_arguments_node_t* arguments = call_node->arguments;
372
+ if (arguments->arguments.size == 0) { return NULL; }
373
+
374
+ pm_node_t* last_argument = arguments->arguments.nodes[arguments->arguments.size - 1];
375
+
376
+ if (last_argument->type != PM_KEYWORD_HASH_NODE) { return NULL; }
377
+
378
+ pm_keyword_hash_node_t* hash = (pm_keyword_hash_node_t*) last_argument;
379
+ hb_buffer_T buffer;
380
+ hb_buffer_init(&buffer, 64, allocator);
381
+ bool first = true;
382
+
383
+ for (size_t i = 0; i < hash->elements.size; i++) {
384
+ pm_node_t* element = hash->elements.nodes[i];
385
+
386
+ if (element->type != PM_ASSOC_NODE) { continue; }
387
+
388
+ pm_assoc_node_t* assoc = (pm_assoc_node_t*) element;
389
+
390
+ if (assoc->key->type != PM_SYMBOL_NODE) { continue; }
391
+
392
+ pm_symbol_node_t* key_node = (pm_symbol_node_t*) assoc->key;
393
+ size_t key_length = pm_string_length(&key_node->unescaped);
394
+ const char* key_source = (const char*) pm_string_source(&key_node->unescaped);
395
+
396
+ bool is_path_option = false;
397
+
398
+ for (const char** option = option_names; *option != NULL; option++) {
399
+ if (key_length == strlen(*option) && strncmp(key_source, *option, key_length) == 0) {
400
+ is_path_option = true;
401
+ break;
402
+ }
403
+ }
404
+
405
+ if (!is_path_option) { continue; }
406
+
407
+ if (!first) { hb_buffer_append(&buffer, ", "); }
408
+ first = false;
409
+
410
+ size_t key_source_length = assoc->key->location.end - assoc->key->location.start;
411
+ hb_buffer_append_with_length(&buffer, (const char*) assoc->key->location.start, key_source_length);
412
+ hb_buffer_append(&buffer, " ");
413
+
414
+ size_t value_source_length = assoc->value->location.end - assoc->value->location.start;
415
+ hb_buffer_append_with_length(&buffer, (const char*) assoc->value->location.start, value_source_length);
416
+ }
417
+
418
+ if (first) {
419
+ hb_buffer_free(&buffer);
420
+ return NULL;
421
+ }
422
+
423
+ char* result = hb_allocator_strdup(allocator, hb_buffer_value(&buffer));
424
+ hb_buffer_free(&buffer);
425
+
426
+ return result;
427
+ }
428
+
429
+ static AST_NODE_T* remove_attribute_by_name(hb_array_T* attributes, const char* name) {
430
+ if (!attributes) { return NULL; }
431
+
432
+ for (size_t index = 0; index < hb_array_size(attributes); index++) {
433
+ AST_NODE_T* attribute = hb_array_get(attributes, index);
434
+
435
+ if (attribute->type != AST_HTML_ATTRIBUTE_NODE) { continue; }
436
+
437
+ AST_HTML_ATTRIBUTE_NODE_T* html_attribute = (AST_HTML_ATTRIBUTE_NODE_T*) attribute;
438
+
439
+ if (!html_attribute->name || !html_attribute->name->children || !hb_array_size(html_attribute->name->children)) {
440
+ continue;
441
+ }
442
+
443
+ AST_LITERAL_NODE_T* literal = (AST_LITERAL_NODE_T*) hb_array_get(html_attribute->name->children, 0);
444
+
445
+ if (hb_string_equals(literal->content, hb_string((char*) name))) {
446
+ hb_array_remove(attributes, index);
447
+ return attribute;
448
+ }
449
+ }
450
+
451
+ return NULL;
452
+ }
233
453
  static AST_NODE_T* transform_tag_helper_with_attributes(
234
454
  AST_ERB_CONTENT_NODE_T* erb_node,
235
455
  analyze_ruby_context_T* context,
@@ -261,6 +481,116 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
261
481
  );
262
482
  }
263
483
 
484
+ if (attributes && handler->name
485
+ && (strcmp(handler->name, "javascript_include_tag") == 0 || strcmp(handler->name, "javascript_tag") == 0)) {
486
+ resolve_nonce_attribute(attributes, allocator);
487
+ }
488
+
489
+ char* path_options = NULL;
490
+
491
+ if (attributes && handler->name && strcmp(handler->name, "javascript_include_tag") == 0) {
492
+ path_options = extract_path_options_from_keyword_hash(
493
+ parse_context->info->call_node,
494
+ JAVASCRIPT_INCLUDE_TAG_PATH_OPTIONS,
495
+ allocator
496
+ );
497
+
498
+ remove_attribute_by_name(attributes, "extname");
499
+ remove_attribute_by_name(attributes, "host");
500
+ remove_attribute_by_name(attributes, "protocol");
501
+ remove_attribute_by_name(attributes, "skip-pipeline");
502
+ }
503
+
504
+ if (attributes && handler->name && strcmp(handler->name, "image_tag") == 0) {
505
+ path_options =
506
+ extract_path_options_from_keyword_hash(parse_context->info->call_node, IMAGE_TAG_PATH_OPTIONS, allocator);
507
+ remove_attribute_by_name(attributes, "skip-pipeline");
508
+
509
+ AST_NODE_T* size_node = remove_attribute_by_name(attributes, "size");
510
+
511
+ if (size_node && size_node->type == AST_HTML_ATTRIBUTE_NODE) {
512
+ AST_HTML_ATTRIBUTE_NODE_T* size_attribute_node = (AST_HTML_ATTRIBUTE_NODE_T*) size_node;
513
+
514
+ if (size_attribute_node->value && size_attribute_node->value->children
515
+ && hb_array_size(size_attribute_node->value->children) > 0) {
516
+ AST_NODE_T* value_node = (AST_NODE_T*) hb_array_get(size_attribute_node->value->children, 0);
517
+ position_T position = size_attribute_node->base.location.start;
518
+
519
+ if (value_node->type == AST_LITERAL_NODE) {
520
+ AST_LITERAL_NODE_T* literal = (AST_LITERAL_NODE_T*) value_node;
521
+ char* size_string = hb_allocator_strndup(allocator, literal->content.data, literal->content.length);
522
+
523
+ if (size_string) {
524
+ const char* x_position = strchr(size_string, 'x');
525
+ char width_string[32];
526
+ char height_string[32];
527
+
528
+ if (x_position) {
529
+ size_t width_length = (size_t) (x_position - size_string);
530
+ strncpy(width_string, size_string, width_length);
531
+ width_string[width_length] = '\0';
532
+ strncpy(height_string, x_position + 1, sizeof(height_string) - 1);
533
+ height_string[sizeof(height_string) - 1] = '\0';
534
+ } else {
535
+ strncpy(width_string, size_string, sizeof(width_string) - 1);
536
+ width_string[sizeof(width_string) - 1] = '\0';
537
+ strncpy(height_string, size_string, sizeof(height_string) - 1);
538
+ height_string[sizeof(height_string) - 1] = '\0';
539
+ }
540
+
541
+ AST_HTML_ATTRIBUTE_NODE_T* width_attribute =
542
+ create_html_attribute_node("width", width_string, position, position, allocator);
543
+ AST_HTML_ATTRIBUTE_NODE_T* height_attribute =
544
+ create_html_attribute_node("height", height_string, position, position, allocator);
545
+
546
+ if (width_attribute) { hb_array_append(attributes, (AST_NODE_T*) width_attribute); }
547
+ if (height_attribute) { hb_array_append(attributes, (AST_NODE_T*) height_attribute); }
548
+
549
+ hb_allocator_dealloc(allocator, size_string);
550
+ }
551
+ } else if (value_node->type == AST_RUBY_LITERAL_NODE) {
552
+ AST_RUBY_LITERAL_NODE_T* ruby_literal = (AST_RUBY_LITERAL_NODE_T*) value_node;
553
+ char* size_expression =
554
+ hb_allocator_strndup(allocator, ruby_literal->content.data, ruby_literal->content.length);
555
+
556
+ if (size_expression) {
557
+ hb_buffer_T width_buffer;
558
+ hb_buffer_init(&width_buffer, ruby_literal->content.length + 32, allocator);
559
+ hb_buffer_append(&width_buffer, size_expression);
560
+ hb_buffer_append(&width_buffer, ".to_s.split(\"x\", 2)[0]");
561
+
562
+ hb_buffer_T height_buffer;
563
+ hb_buffer_init(&height_buffer, ruby_literal->content.length + 32, allocator);
564
+ hb_buffer_append(&height_buffer, size_expression);
565
+ hb_buffer_append(&height_buffer, ".to_s.split(\"x\", 2)[-1]");
566
+
567
+ AST_HTML_ATTRIBUTE_NODE_T* width_attribute = create_html_attribute_with_ruby_literal(
568
+ "width",
569
+ hb_buffer_value(&width_buffer),
570
+ position,
571
+ position,
572
+ allocator
573
+ );
574
+ AST_HTML_ATTRIBUTE_NODE_T* height_attribute = create_html_attribute_with_ruby_literal(
575
+ "height",
576
+ hb_buffer_value(&height_buffer),
577
+ position,
578
+ position,
579
+ allocator
580
+ );
581
+
582
+ if (width_attribute) { hb_array_append(attributes, (AST_NODE_T*) width_attribute); }
583
+ if (height_attribute) { hb_array_append(attributes, (AST_NODE_T*) height_attribute); }
584
+
585
+ hb_buffer_free(&width_buffer);
586
+ hb_buffer_free(&height_buffer);
587
+ hb_allocator_dealloc(allocator, size_expression);
588
+ }
589
+ }
590
+ }
591
+ }
592
+ }
593
+
264
594
  char* helper_content = NULL;
265
595
  bool content_is_ruby_expression = false;
266
596
 
@@ -340,7 +670,7 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
340
670
  quoted_source[quoted_length - 1] = '"';
341
671
  quoted_source[quoted_length] = '\0';
342
672
 
343
- source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, allocator);
673
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, path_options, allocator);
344
674
  hb_allocator_dealloc(allocator, quoted_source);
345
675
  }
346
676
 
@@ -356,6 +686,50 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
356
686
  }
357
687
  }
358
688
 
689
+ if (detect_image_tag(parse_context->info->call_node, &parse_context->parser)
690
+ && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
691
+ char* source_value = extract_image_tag_src(parse_context->info->call_node, &parse_context->parser, allocator);
692
+
693
+ if (source_value) {
694
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
695
+
696
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
697
+ position_T source_start, source_end;
698
+ prism_node_location_to_positions(&first_argument->location, parse_context, &source_start, &source_end);
699
+ bool source_is_string = (first_argument->type == PM_STRING_NODE);
700
+ bool source_is_path_helper = is_route_helper_node(first_argument, &parse_context->parser);
701
+
702
+ size_t source_length = strlen(source_value);
703
+ bool is_url = image_tag_source_is_url(source_value, source_length);
704
+
705
+ char* source_attribute_value = source_value;
706
+
707
+ if (source_is_string && !is_url) {
708
+ size_t quoted_length = source_length + 2;
709
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
710
+ quoted_source[0] = '"';
711
+ memcpy(quoted_source + 1, source_value, source_length);
712
+ quoted_source[quoted_length - 1] = '"';
713
+ quoted_source[quoted_length] = '\0';
714
+
715
+ source_attribute_value = wrap_in_image_path(quoted_source, quoted_length, path_options, allocator);
716
+ hb_allocator_dealloc(allocator, quoted_source);
717
+ } else if (!source_is_string && !is_url && !source_is_path_helper) {
718
+ source_attribute_value = wrap_in_image_path(source_value, source_length, path_options, allocator);
719
+ }
720
+
721
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
722
+ is_url
723
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
724
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
725
+
726
+ if (source_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) source_attribute, allocator); }
727
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
728
+
729
+ hb_allocator_dealloc(allocator, source_value);
730
+ }
731
+ }
732
+
359
733
  token_T* tag_name_token =
360
734
  tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
361
735
 
@@ -374,9 +748,56 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
374
748
  );
375
749
 
376
750
  hb_array_T* body = hb_array_init(1, allocator);
377
- bool is_void = tag_name && (strcmp(handler->name, "tag") == 0) && is_void_element(hb_string_from_c_string(tag_name));
751
+ hb_array_T* element_errors = hb_array_init(0, allocator);
752
+ bool is_void = tag_name && is_void_element(hb_string_from_c_string(tag_name))
753
+ && (string_equals(handler->name, "tag") || string_equals(handler->name, "content_tag")
754
+ || string_equals(handler->name, "image_tag"));
378
755
 
379
756
  if (helper_content) {
757
+ if (is_void && tag_name) {
758
+ hb_buffer_T helper_name_buffer;
759
+ hb_buffer_init(&helper_name_buffer, 64, allocator);
760
+
761
+ if (string_equals(handler->name, "tag")) {
762
+ hb_buffer_append(&helper_name_buffer, "tag.");
763
+ hb_buffer_append(&helper_name_buffer, tag_name);
764
+ } else {
765
+ hb_buffer_append(&helper_name_buffer, handler->name);
766
+ hb_buffer_append(&helper_name_buffer, " :");
767
+ hb_buffer_append(&helper_name_buffer, tag_name);
768
+ }
769
+
770
+ hb_string_T helper_name = hb_string_from_c_string(hb_buffer_value(&helper_name_buffer));
771
+
772
+ position_T content_start = erb_node->base.location.start;
773
+ position_T content_end = erb_node->base.location.end;
774
+
775
+ pm_call_node_t* call = parse_context->info->call_node;
776
+
777
+ if (call && call->arguments) {
778
+ size_t content_arg_index = string_equals(handler->name, "content_tag") ? 1 : 0;
779
+
780
+ if (call->arguments->arguments.size > content_arg_index) {
781
+ pm_node_t* content_node = call->arguments->arguments.nodes[content_arg_index];
782
+ prism_node_location_to_positions(&content_node->location, parse_context, &content_start, &content_end);
783
+ }
784
+ }
785
+
786
+ pm_call_node_t* void_call = parse_context->info->call_node;
787
+ bool content_from_block = void_call && void_call->block && void_call->block->type == PM_BLOCK_NODE;
788
+ hb_string_T content_type = content_from_block ? hb_string("a block") : hb_string("a positional argument");
789
+
790
+ append_void_element_content_error(
791
+ tag_name_token,
792
+ helper_name,
793
+ content_type,
794
+ content_start,
795
+ content_end,
796
+ allocator,
797
+ element_errors
798
+ );
799
+ }
800
+
380
801
  append_body_content_node(
381
802
  body,
382
803
  helper_content,
@@ -410,7 +831,7 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
410
831
  handler->source,
411
832
  erb_node->base.location.start,
412
833
  erb_node->base.location.end,
413
- hb_array_init(0, allocator),
834
+ element_errors,
414
835
  allocator
415
836
  );
416
837
 
@@ -438,6 +859,7 @@ static AST_NODE_T* create_javascript_include_tag_element(
438
859
  tag_helper_parse_context_T* parse_context,
439
860
  pm_node_t* source_argument,
440
861
  hb_array_T* shared_attributes,
862
+ const char* path_options,
441
863
  hb_allocator_T* allocator
442
864
  ) {
443
865
  position_T tag_name_start, tag_name_end;
@@ -478,7 +900,7 @@ static AST_NODE_T* create_javascript_include_tag_element(
478
900
  quoted_source[quoted_length - 1] = '"';
479
901
  quoted_source[quoted_length] = '\0';
480
902
 
481
- source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, allocator);
903
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, path_options, allocator);
482
904
  hb_allocator_dealloc(allocator, quoted_source);
483
905
  }
484
906
 
@@ -551,12 +973,27 @@ static hb_array_T* transform_javascript_include_tag_multi_source(
551
973
  );
552
974
  if (!shared_attributes) { shared_attributes = hb_array_init(0, allocator); }
553
975
 
976
+ resolve_nonce_attribute(shared_attributes, allocator);
977
+
978
+ char* path_options =
979
+ extract_path_options_from_keyword_hash(call_node, JAVASCRIPT_INCLUDE_TAG_PATH_OPTIONS, allocator);
980
+ remove_attribute_by_name(shared_attributes, "extname");
981
+ remove_attribute_by_name(shared_attributes, "host");
982
+ remove_attribute_by_name(shared_attributes, "protocol");
983
+ remove_attribute_by_name(shared_attributes, "skip-pipeline");
984
+
554
985
  hb_array_T* elements = hb_array_init(source_count * 2, allocator);
555
986
 
556
987
  for (size_t i = 0; i < source_count; i++) {
557
988
  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);
989
+ AST_NODE_T* element = create_javascript_include_tag_element(
990
+ erb_node,
991
+ parse_context,
992
+ source_arg,
993
+ shared_attributes,
994
+ path_options,
995
+ allocator
996
+ );
560
997
 
561
998
  if (element) {
562
999
  if (hb_array_size(elements) > 0) {
@@ -578,6 +1015,42 @@ static hb_array_T* transform_javascript_include_tag_multi_source(
578
1015
  return elements;
579
1016
  }
580
1017
 
1018
+ static bool erb_content_is_end_keyword(hb_string_T content) {
1019
+ const char* start = content.data;
1020
+ const char* end = content.data + content.length;
1021
+
1022
+ while (start < end && is_whitespace(*start)) {
1023
+ start++;
1024
+ }
1025
+
1026
+ while (end > start && is_whitespace(*(end - 1))) {
1027
+ end--;
1028
+ }
1029
+
1030
+ return (size_t) (end - start) == 3 && start[0] == 'e' && start[1] == 'n' && start[2] == 'd';
1031
+ }
1032
+
1033
+ static AST_ERB_CONTENT_NODE_T* find_swallowed_erb_end_node(hb_array_T* nodes) {
1034
+ if (!nodes) { return NULL; }
1035
+
1036
+ for (size_t i = 0; i < hb_array_size(nodes); i++) {
1037
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1038
+ if (!node) { continue; }
1039
+
1040
+ if (node->type == AST_ERB_CONTENT_NODE) {
1041
+ AST_ERB_CONTENT_NODE_T* erb = (AST_ERB_CONTENT_NODE_T*) node;
1042
+ if (erb->content && erb_content_is_end_keyword(erb->content->value)) { return erb; }
1043
+ }
1044
+
1045
+ if (node->type == AST_HTML_OPEN_TAG_NODE) {
1046
+ AST_ERB_CONTENT_NODE_T* found = find_swallowed_erb_end_node(((AST_HTML_OPEN_TAG_NODE_T*) node)->children);
1047
+ if (found) { return found; }
1048
+ }
1049
+ }
1050
+
1051
+ return NULL;
1052
+ }
1053
+
581
1054
  static AST_NODE_T* transform_erb_block_to_tag_helper(
582
1055
  AST_ERB_BLOCK_NODE_T* block_node,
583
1056
  analyze_ruby_context_T* context,
@@ -598,6 +1071,7 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
598
1071
  );
599
1072
 
600
1073
  hb_array_T* attributes = NULL;
1074
+
601
1075
  if (parse_context->info->call_node) {
602
1076
  attributes = extract_html_attributes_from_call_node(
603
1077
  parse_context->info->call_node,
@@ -608,6 +1082,12 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
608
1082
  );
609
1083
  }
610
1084
 
1085
+ if (attributes && parse_context->matched_handler && parse_context->matched_handler->name
1086
+ && (strcmp(parse_context->matched_handler->name, "javascript_include_tag") == 0
1087
+ || strcmp(parse_context->matched_handler->name, "javascript_tag") == 0)) {
1088
+ resolve_nonce_attribute(attributes, allocator);
1089
+ }
1090
+
611
1091
  if (detect_link_to(parse_context->info->call_node, &parse_context->parser)
612
1092
  && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
613
1093
  pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
@@ -682,26 +1162,85 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
682
1162
  );
683
1163
 
684
1164
  hb_array_T* body = block_node->body ? block_node->body : hb_array_init(0, allocator);
1165
+ hb_array_T* element_errors = hb_array_init(0, allocator);
685
1166
  AST_NODE_T* close_tag = (AST_NODE_T*) block_node->end_node;
1167
+ position_T element_end = block_node->base.location.end;
1168
+
1169
+ bool is_void = tag_name && is_void_element(hb_string_from_c_string(tag_name)) && parse_context->matched_handler
1170
+ && parse_context->matched_handler->name
1171
+ && (string_equals(parse_context->matched_handler->name, "tag")
1172
+ || string_equals(parse_context->matched_handler->name, "content_tag")
1173
+ || string_equals(parse_context->matched_handler->name, "image_tag"));
1174
+
1175
+ if (is_void) {
1176
+ hb_buffer_T helper_name_buffer;
1177
+ hb_buffer_init(&helper_name_buffer, 64, allocator);
1178
+
1179
+ if (string_equals(parse_context->matched_handler->name, "tag")) {
1180
+ hb_buffer_append(&helper_name_buffer, "tag.");
1181
+ hb_buffer_append(&helper_name_buffer, tag_name);
1182
+ } else {
1183
+ hb_buffer_append(&helper_name_buffer, parse_context->matched_handler->name);
1184
+ hb_buffer_append(&helper_name_buffer, " :");
1185
+ hb_buffer_append(&helper_name_buffer, tag_name);
1186
+ }
1187
+
1188
+ hb_string_T helper_name = hb_string_from_c_string(hb_buffer_value(&helper_name_buffer));
1189
+
1190
+ append_void_element_content_error(
1191
+ tag_name_token,
1192
+ helper_name,
1193
+ hb_string("a block"),
1194
+ block_node->base.location.start,
1195
+ block_node->base.location.end,
1196
+ allocator,
1197
+ element_errors
1198
+ );
1199
+ }
686
1200
 
687
1201
  if (tag_name && parser_is_foreign_content_tag(hb_string_from_c_string(tag_name)) && context->source
688
1202
  && block_node->body && hb_array_size(block_node->body) > 0) {
689
1203
  size_t start_offset = block_node->tag_closing->range.to;
690
- size_t end_offset = block_node->end_node->tag_opening->range.from;
1204
+ size_t end_offset = 0;
1205
+
1206
+ if (block_node->end_node && block_node->end_node->tag_opening) {
1207
+ end_offset = block_node->end_node->tag_opening->range.from;
1208
+ } else {
1209
+ AST_ERB_CONTENT_NODE_T* swallowed_end = find_swallowed_erb_end_node(block_node->body);
1210
+
1211
+ if (swallowed_end && swallowed_end->tag_opening) {
1212
+ end_offset = swallowed_end->tag_opening->range.from;
1213
+
1214
+ AST_ERB_END_NODE_T* end_node = ast_erb_end_node_init(
1215
+ swallowed_end->tag_opening,
1216
+ swallowed_end->content,
1217
+ swallowed_end->tag_closing,
1218
+ swallowed_end->base.location.start,
1219
+ swallowed_end->base.location.end,
1220
+ hb_array_init(0, allocator),
1221
+ allocator
1222
+ );
1223
+
1224
+ close_tag = (AST_NODE_T*) end_node;
1225
+ element_end = close_tag->location.end;
1226
+ }
1227
+ }
691
1228
 
692
1229
  if (end_offset > start_offset) {
693
1230
  position_T body_start = block_node->tag_closing->location.end;
694
- position_T body_end = block_node->end_node->tag_opening->location.start;
695
1231
 
696
1232
  size_t content_length = end_offset - start_offset;
697
1233
  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
1234
 
700
- AST_LITERAL_NODE_T* literal_node =
701
- ast_literal_node_init(raw_content, body_start, body_end, hb_array_init(0, allocator), allocator);
1235
+ parser_options_T body_options = HERB_DEFAULT_PARSER_OPTIONS;
1236
+ body_options.html = false;
1237
+ body_options.analyze = false;
1238
+ body_options.strict = false;
1239
+ body_options.start_line = body_start.line;
1240
+ body_options.start_column = body_start.column;
702
1241
 
703
- body = hb_array_init(1, allocator);
704
- hb_array_append(body, literal_node);
1242
+ AST_DOCUMENT_NODE_T* body_document = herb_parse(raw_copy, &body_options, allocator);
1243
+ body = body_document->children;
705
1244
  }
706
1245
  }
707
1246
 
@@ -709,12 +1248,12 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
709
1248
  (AST_NODE_T*) open_tag_node,
710
1249
  tag_name_token,
711
1250
  body,
712
- close_tag,
713
- false,
1251
+ is_void ? NULL : close_tag,
1252
+ is_void,
714
1253
  parse_context->matched_handler->source,
715
1254
  block_node->base.location.start,
716
- block_node->base.location.end,
717
- hb_array_init(0, allocator),
1255
+ element_end,
1256
+ element_errors,
718
1257
  allocator
719
1258
  );
720
1259
 
@@ -763,6 +1302,12 @@ static AST_NODE_T* transform_link_to_helper(
763
1302
  char* content = hb_allocator_strndup(allocator, (const char*) second_arg->location.start, source_length);
764
1303
 
765
1304
  if (content) {
1305
+ hb_buffer_T wrapped;
1306
+ hb_buffer_init(&wrapped, source_length + 32, allocator);
1307
+ hb_buffer_append(&wrapped, "tag.attributes(**");
1308
+ hb_buffer_append(&wrapped, content);
1309
+ hb_buffer_append(&wrapped, ")");
1310
+
766
1311
  position_T position = prism_location_to_position_with_offset(
767
1312
  &second_arg->location,
768
1313
  parse_context->original_source,
@@ -771,7 +1316,7 @@ static AST_NODE_T* transform_link_to_helper(
771
1316
  );
772
1317
 
773
1318
  AST_RUBY_HTML_ATTRIBUTES_SPLAT_NODE_T* splat_node = ast_ruby_html_attributes_splat_node_init(
774
- hb_string_from_c_string(content),
1319
+ hb_string_from_c_string(hb_buffer_value(&wrapped)),
775
1320
  HB_STRING_EMPTY,
776
1321
  position,
777
1322
  position,
@@ -781,6 +1326,7 @@ static AST_NODE_T* transform_link_to_helper(
781
1326
 
782
1327
  if (splat_node) { hb_array_append(attributes, (AST_NODE_T*) splat_node); }
783
1328
 
1329
+ hb_buffer_free(&wrapped);
784
1330
  hb_allocator_dealloc(allocator, content);
785
1331
  }
786
1332
  }
@@ -946,21 +1492,8 @@ static AST_NODE_T* transform_link_to_helper(
946
1492
  return (AST_NODE_T*) element;
947
1493
  }
948
1494
 
949
- void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
950
- if (!node || !context) { return; }
951
-
952
- hb_array_T* array = NULL;
953
-
954
- switch (node->type) {
955
- case AST_DOCUMENT_NODE: array = ((AST_DOCUMENT_NODE_T*) node)->children; break;
956
- case AST_HTML_ELEMENT_NODE: array = ((AST_HTML_ELEMENT_NODE_T*) node)->body; break;
957
- case AST_HTML_OPEN_TAG_NODE: array = ((AST_HTML_OPEN_TAG_NODE_T*) node)->children; break;
958
- case AST_HTML_ATTRIBUTE_VALUE_NODE: array = ((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children; break;
959
- case AST_ERB_BLOCK_NODE: array = ((AST_ERB_BLOCK_NODE_T*) node)->body; break;
960
- default: return;
961
- }
962
-
963
- if (!array) { return; }
1495
+ void transform_tag_helper_array(hb_array_T* array, analyze_ruby_context_T* context) {
1496
+ if (!array || !context) { return; }
964
1497
 
965
1498
  for (size_t i = 0; i < hb_array_size(array); i++) {
966
1499
  AST_NODE_T* child = hb_array_get(array, i);
@@ -985,6 +1518,7 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
985
1518
 
986
1519
  if (parse_context) {
987
1520
  replacement = transform_erb_block_to_tag_helper(block_node, context, parse_context);
1521
+ replacement = wrap_in_conditional_if_needed(replacement, parse_context, context->allocator);
988
1522
  free_tag_helper_parse_context(parse_context);
989
1523
  }
990
1524
 
@@ -1042,18 +1576,232 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
1042
1576
  }
1043
1577
  } else if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
1044
1578
  replacement = transform_link_to_helper(erb_node, context, parse_context);
1579
+ } else if (string_equals(parse_context->matched_handler->name, "tag") && parse_context->info->tag_name
1580
+ && string_equals(parse_context->info->tag_name, "attributes")) {
1581
+ hb_array_T* attributes = NULL;
1582
+
1583
+ if (parse_context->info->call_node) {
1584
+ attributes = extract_html_attributes_from_call_node(
1585
+ parse_context->info->call_node,
1586
+ parse_context->prism_source,
1587
+ parse_context->original_source,
1588
+ parse_context->erb_content_offset,
1589
+ context->allocator
1590
+ );
1591
+ }
1592
+
1593
+ if (attributes && hb_array_size(attributes) > 0) {
1594
+ size_t old_size = hb_array_size(array);
1595
+ size_t attributes_size = hb_array_size(attributes);
1596
+ hb_array_T* new_array = hb_array_init(old_size - 1 + attributes_size, context->allocator);
1597
+
1598
+ for (size_t j = 0; j < old_size; j++) {
1599
+ if (j == i) {
1600
+ for (size_t k = 0; k < attributes_size; k++) {
1601
+ hb_array_append(new_array, hb_array_get(attributes, k));
1602
+ }
1603
+ } else {
1604
+ hb_array_append(new_array, hb_array_get(array, j));
1605
+ }
1606
+ }
1607
+
1608
+ array->items = new_array->items;
1609
+ array->size = new_array->size;
1610
+ array->capacity = new_array->capacity;
1611
+
1612
+ i += attributes_size - 1;
1613
+ }
1045
1614
  } else {
1046
1615
  replacement = transform_tag_helper_with_attributes(erb_node, context, parse_context);
1047
1616
  }
1048
1617
 
1618
+ replacement = wrap_in_conditional_if_needed(replacement, parse_context, context->allocator);
1049
1619
  free_tag_helper_parse_context(parse_context);
1050
1620
  }
1051
1621
 
1052
1622
  free(erb_string);
1053
1623
  }
1624
+ } else if (child->type == AST_HTML_ATTRIBUTE_NODE) {
1625
+ AST_HTML_ATTRIBUTE_NODE_T* attribute_node = (AST_HTML_ATTRIBUTE_NODE_T*) child;
1626
+
1627
+ if (attribute_node->name && !attribute_node->equals && !attribute_node->value && attribute_node->name->children
1628
+ && hb_array_size(attribute_node->name->children) == 1) {
1629
+ AST_NODE_T* name_child = hb_array_get(attribute_node->name->children, 0);
1630
+
1631
+ if (name_child && name_child->type == AST_ERB_CONTENT_NODE) {
1632
+ AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) name_child;
1633
+ token_T* erb_content = erb_node->content;
1634
+
1635
+ if (erb_content && !hb_string_is_empty(erb_content->value)) {
1636
+ char* erb_string = hb_string_to_c_string_using_malloc(erb_content->value);
1637
+ size_t erb_content_offset = 0;
1638
+
1639
+ if (context->source) {
1640
+ erb_content_offset = calculate_byte_offset_from_position(context->source, erb_content->location.start);
1641
+ }
1642
+
1643
+ tag_helper_parse_context_T* parse_context =
1644
+ parse_tag_helper_content(erb_string, context->source, erb_content_offset, context->allocator);
1645
+
1646
+ if (parse_context && string_equals(parse_context->matched_handler->name, "tag")
1647
+ && parse_context->info->tag_name && string_equals(parse_context->info->tag_name, "attributes")) {
1648
+ hb_array_T* attributes = NULL;
1649
+
1650
+ if (parse_context->info->call_node) {
1651
+ attributes = extract_html_attributes_from_call_node(
1652
+ parse_context->info->call_node,
1653
+ parse_context->prism_source,
1654
+ parse_context->original_source,
1655
+ parse_context->erb_content_offset,
1656
+ context->allocator
1657
+ );
1658
+ }
1659
+
1660
+ if (attributes && hb_array_size(attributes) > 0) {
1661
+ size_t old_size = hb_array_size(array);
1662
+ size_t attributes_size = hb_array_size(attributes);
1663
+ hb_array_T* new_array = hb_array_init(old_size - 1 + attributes_size, context->allocator);
1664
+
1665
+ for (size_t j = 0; j < old_size; j++) {
1666
+ if (j == i) {
1667
+ for (size_t k = 0; k < attributes_size; k++) {
1668
+ hb_array_append(new_array, hb_array_get(attributes, k));
1669
+ }
1670
+ } else {
1671
+ hb_array_append(new_array, hb_array_get(array, j));
1672
+ }
1673
+ }
1674
+
1675
+ array->items = new_array->items;
1676
+ array->size = new_array->size;
1677
+ array->capacity = new_array->capacity;
1678
+
1679
+ i += attributes_size - 1;
1680
+ }
1681
+
1682
+ free_tag_helper_parse_context(parse_context);
1683
+ } else if (parse_context) {
1684
+ free_tag_helper_parse_context(parse_context);
1685
+ }
1686
+
1687
+ free(erb_string);
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1692
+
1693
+ if (replacement) {
1694
+ position_T replacement_end = replacement->location.end;
1695
+ position_T original_end = child->location.end;
1696
+ bool has_trailing = replacement_end.line != original_end.line || replacement_end.column != original_end.column;
1697
+
1698
+ if (has_trailing && context->source && child->type == AST_ERB_BLOCK_NODE) {
1699
+ AST_HTML_ELEMENT_NODE_T* element = (AST_HTML_ELEMENT_NODE_T*) replacement;
1700
+
1701
+ if (replacement->type == AST_ERB_IF_NODE) {
1702
+ AST_ERB_IF_NODE_T* if_node = (AST_ERB_IF_NODE_T*) replacement;
1703
+
1704
+ if (if_node->statements && hb_array_size(if_node->statements) > 0) {
1705
+ element = (AST_HTML_ELEMENT_NODE_T*) hb_array_get(if_node->statements, 0);
1706
+ }
1707
+ } else if (replacement->type == AST_ERB_UNLESS_NODE) {
1708
+ AST_ERB_UNLESS_NODE_T* unless_node = (AST_ERB_UNLESS_NODE_T*) replacement;
1709
+
1710
+ if (unless_node->statements && hb_array_size(unless_node->statements) > 0) {
1711
+ element = (AST_HTML_ELEMENT_NODE_T*) hb_array_get(unless_node->statements, 0);
1712
+ }
1713
+ }
1714
+
1715
+ if (element->close_tag && element->close_tag->type == AST_ERB_END_NODE) {
1716
+ AST_ERB_END_NODE_T* close_erb = (AST_ERB_END_NODE_T*) element->close_tag;
1717
+ size_t trailing_start = close_erb->tag_closing->range.to;
1718
+ size_t source_length = strlen(context->source);
1719
+ size_t trailing_end = trailing_start;
1720
+
1721
+ while (trailing_end < source_length) {
1722
+ position_T position = position_from_source_with_offset(context->source, trailing_end);
1723
+
1724
+ if (position.line > original_end.line
1725
+ || (position.line == original_end.line && position.column >= original_end.column)) {
1726
+ break;
1727
+ }
1728
+
1729
+ trailing_end++;
1730
+ }
1731
+
1732
+ if (trailing_end > trailing_start) {
1733
+ hb_string_T trailing_content =
1734
+ hb_string_from_data(context->source + trailing_start, trailing_end - trailing_start);
1735
+ AST_HTML_TEXT_NODE_T* trailing_text = ast_html_text_node_init(
1736
+ trailing_content,
1737
+ replacement_end,
1738
+ original_end,
1739
+ hb_array_init(0, context->allocator),
1740
+ context->allocator
1741
+ );
1742
+
1743
+ size_t old_size = hb_array_size(array);
1744
+ hb_array_T* new_array = hb_array_init(old_size + 1, context->allocator);
1745
+
1746
+ for (size_t j = 0; j < old_size; j++) {
1747
+ if (j == i) {
1748
+ hb_array_append(new_array, replacement);
1749
+ hb_array_append(new_array, trailing_text);
1750
+ } else {
1751
+ hb_array_append(new_array, hb_array_get(array, j));
1752
+ }
1753
+ }
1754
+
1755
+ array->items = new_array->items;
1756
+ array->size = new_array->size;
1757
+ array->capacity = new_array->capacity;
1758
+ i++;
1759
+ continue;
1760
+ }
1761
+ }
1762
+ }
1763
+
1764
+ hb_array_set(array, i, replacement);
1054
1765
  }
1766
+ }
1767
+ }
1768
+
1769
+ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
1770
+ if (!node || !context) { return; }
1055
1771
 
1056
- if (replacement) { hb_array_set(array, i, replacement); }
1772
+ switch (node->type) {
1773
+ case AST_DOCUMENT_NODE: transform_tag_helper_array(((AST_DOCUMENT_NODE_T*) node)->children, context); break;
1774
+ case AST_HTML_ELEMENT_NODE: transform_tag_helper_array(((AST_HTML_ELEMENT_NODE_T*) node)->body, context); break;
1775
+ case AST_HTML_CONDITIONAL_ELEMENT_NODE:
1776
+ transform_tag_helper_array(((AST_HTML_CONDITIONAL_ELEMENT_NODE_T*) node)->body, context);
1777
+ break;
1778
+ case AST_HTML_OPEN_TAG_NODE:
1779
+ transform_tag_helper_array(((AST_HTML_OPEN_TAG_NODE_T*) node)->children, context);
1780
+ break;
1781
+ case AST_HTML_ATTRIBUTE_VALUE_NODE:
1782
+ transform_tag_helper_array(((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children, context);
1783
+ break;
1784
+ case AST_ERB_BLOCK_NODE: transform_tag_helper_array(((AST_ERB_BLOCK_NODE_T*) node)->body, context); break;
1785
+ case AST_ERB_IF_NODE: transform_tag_helper_array(((AST_ERB_IF_NODE_T*) node)->statements, context); break;
1786
+ case AST_ERB_ELSE_NODE: transform_tag_helper_array(((AST_ERB_ELSE_NODE_T*) node)->statements, context); break;
1787
+ case AST_ERB_UNLESS_NODE: transform_tag_helper_array(((AST_ERB_UNLESS_NODE_T*) node)->statements, context); break;
1788
+ case AST_ERB_CASE_NODE:
1789
+ transform_tag_helper_array(((AST_ERB_CASE_NODE_T*) node)->children, context);
1790
+ transform_tag_helper_array(((AST_ERB_CASE_NODE_T*) node)->conditions, context);
1791
+ break;
1792
+ case AST_ERB_CASE_MATCH_NODE:
1793
+ transform_tag_helper_array(((AST_ERB_CASE_MATCH_NODE_T*) node)->children, context);
1794
+ transform_tag_helper_array(((AST_ERB_CASE_MATCH_NODE_T*) node)->conditions, context);
1795
+ break;
1796
+ case AST_ERB_WHEN_NODE: transform_tag_helper_array(((AST_ERB_WHEN_NODE_T*) node)->statements, context); break;
1797
+ case AST_ERB_WHILE_NODE: transform_tag_helper_array(((AST_ERB_WHILE_NODE_T*) node)->statements, context); break;
1798
+ case AST_ERB_UNTIL_NODE: transform_tag_helper_array(((AST_ERB_UNTIL_NODE_T*) node)->statements, context); break;
1799
+ case AST_ERB_FOR_NODE: transform_tag_helper_array(((AST_ERB_FOR_NODE_T*) node)->statements, context); break;
1800
+ case AST_ERB_BEGIN_NODE: transform_tag_helper_array(((AST_ERB_BEGIN_NODE_T*) node)->statements, context); break;
1801
+ case AST_ERB_RESCUE_NODE: transform_tag_helper_array(((AST_ERB_RESCUE_NODE_T*) node)->statements, context); break;
1802
+ case AST_ERB_ENSURE_NODE: transform_tag_helper_array(((AST_ERB_ENSURE_NODE_T*) node)->statements, context); break;
1803
+ case AST_ERB_IN_NODE: transform_tag_helper_array(((AST_ERB_IN_NODE_T*) node)->statements, context); break;
1804
+ default: break;
1057
1805
  }
1058
1806
  }
1059
1807