herb 0.9.1-aarch64-linux-gnu → 0.9.3-aarch64-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 +136 -97
  20. data/lib/herb/engine/validators/security_validator.rb +40 -0
  21. data/lib/herb/engine.rb +21 -0
  22. data/lib/herb/errors.rb +268 -0
  23. data/lib/herb/parser_options.rb +7 -2
  24. data/lib/herb/version.rb +1 -1
  25. data/lib/herb/visitor.rb +82 -0
  26. data/lib/herb.rb +1 -0
  27. data/sig/herb/ast/erb_content_node.rbs +13 -0
  28. data/sig/herb/ast/nodes.rbs +98 -2
  29. data/sig/herb/engine/compiler.rbs +18 -3
  30. data/sig/herb/engine/validators/security_validator.rbs +4 -0
  31. data/sig/herb/engine.rbs +4 -0
  32. data/sig/herb/errors.rbs +122 -0
  33. data/sig/herb/parser_options.rbs +6 -2
  34. data/sig/herb/visitor.rbs +12 -0
  35. data/sig/serialized_ast_errors.rbs +29 -0
  36. data/sig/serialized_ast_nodes.rbs +19 -0
  37. data/src/analyze/action_view/attribute_extraction_helpers.c +425 -87
  38. data/src/analyze/action_view/image_tag.c +87 -0
  39. data/src/analyze/action_view/javascript_include_tag.c +102 -0
  40. data/src/analyze/action_view/javascript_tag.c +55 -0
  41. data/src/analyze/action_view/registry.c +10 -3
  42. data/src/analyze/action_view/tag.c +19 -2
  43. data/src/analyze/action_view/tag_helper_node_builders.c +119 -37
  44. data/src/analyze/action_view/tag_helpers.c +1033 -32
  45. data/src/analyze/analyze.c +165 -10
  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 +5 -5
  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 +637 -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 +6 -4
  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} +2 -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 +4 -8
  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} +54 -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 +78 -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,12 +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/position.h"
9
- #include "../../include/util/hb_allocator.h"
10
- #include "../../include/util/hb_array.h"
11
- #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"
12
17
  #include "../../include/visitor.h"
13
18
 
14
19
  #include <prism.h>
@@ -22,6 +27,14 @@ extern char* wrap_in_url_for(const char*, size_t, hb_allocator_T*);
22
27
  extern char* extract_link_to_href(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
23
28
  extern bool detect_turbo_frame_tag(pm_call_node_t*, pm_parser_t*);
24
29
  extern char* extract_turbo_frame_tag_id(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
30
+ extern bool detect_javascript_include_tag(pm_call_node_t*, pm_parser_t*);
31
+ extern char* extract_javascript_include_tag_src(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
32
+ extern char* wrap_in_javascript_path(const char*, size_t, const char*, hb_allocator_T*);
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);
25
38
 
26
39
  typedef struct {
27
40
  pm_parser_t parser;
@@ -32,6 +45,8 @@ typedef struct {
32
45
  const tag_helper_handler_T* matched_handler;
33
46
  const char* original_source;
34
47
  size_t erb_content_offset;
48
+ char* condition_source;
49
+ char* condition_type;
35
50
  } tag_helper_parse_context_T;
36
51
 
37
52
  static tag_helper_parse_context_T* parse_tag_helper_content(
@@ -61,11 +76,15 @@ static tag_helper_parse_context_T* parse_tag_helper_content(
61
76
  }
62
77
 
63
78
  parse_context->info = tag_helper_info_init(allocator);
79
+ parse_context->condition_source = NULL;
80
+ parse_context->condition_type = NULL;
81
+
64
82
  tag_helper_search_data_T search = { .tag_helper_node = NULL,
65
83
  .source = parse_context->prism_source,
66
84
  .parser = &parse_context->parser,
67
85
  .info = parse_context->info,
68
- .found = false };
86
+ .found = false,
87
+ .postfix_conditional_node = NULL };
69
88
  pm_visit_node(parse_context->root, search_tag_helper_node, &search);
70
89
 
71
90
  if (!search.found) {
@@ -77,6 +96,22 @@ static tag_helper_parse_context_T* parse_tag_helper_content(
77
96
  return NULL;
78
97
  }
79
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
+
80
115
  parse_context->matched_handler = search.matched_handler;
81
116
  return parse_context;
82
117
  }
@@ -96,6 +131,92 @@ static void free_tag_helper_parse_context(tag_helper_parse_context_T* parse_cont
96
131
  }
97
132
  }
98
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
+
99
220
  bool search_tag_helper_node(const pm_node_t* node, void* data) {
100
221
  tag_helper_search_data_T* search_data = (tag_helper_search_data_T*) data;
101
222
 
@@ -119,9 +240,21 @@ bool search_tag_helper_node(const pm_node_t* node, void* data) {
119
240
  search_data->info->has_block = handlers[i].supports_block();
120
241
  }
121
242
 
122
- return true;
243
+ return false;
123
244
  }
124
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;
125
258
  }
126
259
 
127
260
  pm_visit_child_nodes(node, search_tag_helper_node, search_data);
@@ -225,6 +358,98 @@ static void calculate_tag_name_positions(
225
358
  }
226
359
  }
227
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
+ }
228
453
  static AST_NODE_T* transform_tag_helper_with_attributes(
229
454
  AST_ERB_CONTENT_NODE_T* erb_node,
230
455
  analyze_ruby_context_T* context,
@@ -256,6 +481,116 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
256
481
  );
257
482
  }
258
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
+
259
594
  char* helper_content = NULL;
260
595
  bool content_is_ruby_expression = false;
261
596
 
@@ -310,6 +645,91 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
310
645
  }
311
646
  }
312
647
 
648
+ if (detect_javascript_include_tag(parse_context->info->call_node, &parse_context->parser)
649
+ && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
650
+ char* source_value =
651
+ extract_javascript_include_tag_src(parse_context->info->call_node, &parse_context->parser, allocator);
652
+
653
+ if (source_value) {
654
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
655
+
656
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
657
+ position_T source_start, source_end;
658
+ prism_node_location_to_positions(&first_argument->location, parse_context, &source_start, &source_end);
659
+ bool source_is_string = (first_argument->type == PM_STRING_NODE);
660
+
661
+ size_t source_length = strlen(source_value);
662
+ bool is_url = javascript_include_tag_source_is_url(source_value, source_length);
663
+
664
+ char* source_attribute_value = source_value;
665
+ if (source_is_string && !is_url) {
666
+ size_t quoted_length = source_length + 2;
667
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
668
+ quoted_source[0] = '"';
669
+ memcpy(quoted_source + 1, source_value, source_length);
670
+ quoted_source[quoted_length - 1] = '"';
671
+ quoted_source[quoted_length] = '\0';
672
+
673
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, path_options, allocator);
674
+ hb_allocator_dealloc(allocator, quoted_source);
675
+ }
676
+
677
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
678
+ is_url
679
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
680
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
681
+
682
+ if (source_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) source_attribute, allocator); }
683
+
684
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
685
+ hb_allocator_dealloc(allocator, source_value);
686
+ }
687
+ }
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
+
313
733
  token_T* tag_name_token =
314
734
  tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
315
735
 
@@ -328,9 +748,56 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
328
748
  );
329
749
 
330
750
  hb_array_T* body = hb_array_init(1, allocator);
331
- bool is_void = tag_name && (strcmp(handler->name, "tag") == 0) && is_void_element(hb_string_from_c_string(tag_name));
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"));
332
755
 
333
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
+
334
801
  append_body_content_node(
335
802
  body,
336
803
  helper_content,
@@ -364,7 +831,7 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
364
831
  handler->source,
365
832
  erb_node->base.location.start,
366
833
  erb_node->base.location.end,
367
- hb_array_init(0, allocator),
834
+ element_errors,
368
835
  allocator
369
836
  );
370
837
 
@@ -372,6 +839,218 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
372
839
  return (AST_NODE_T*) element;
373
840
  }
374
841
 
842
+ static size_t count_javascript_include_tag_sources(pm_call_node_t* call_node) {
843
+ if (!call_node || !call_node->arguments) { return 0; }
844
+
845
+ size_t count = 0;
846
+ pm_arguments_node_t* arguments = call_node->arguments;
847
+
848
+ for (size_t i = 0; i < arguments->arguments.size; i++) {
849
+ pm_node_t* arg = arguments->arguments.nodes[i];
850
+ if (arg->type == PM_KEYWORD_HASH_NODE) { break; }
851
+ count++;
852
+ }
853
+
854
+ return count;
855
+ }
856
+
857
+ static AST_NODE_T* create_javascript_include_tag_element(
858
+ AST_ERB_CONTENT_NODE_T* erb_node,
859
+ tag_helper_parse_context_T* parse_context,
860
+ pm_node_t* source_argument,
861
+ hb_array_T* shared_attributes,
862
+ const char* path_options,
863
+ hb_allocator_T* allocator
864
+ ) {
865
+ position_T tag_name_start, tag_name_end;
866
+ calculate_tag_name_positions(
867
+ parse_context,
868
+ erb_node->base.location.start,
869
+ erb_node->base.location.end,
870
+ &tag_name_start,
871
+ &tag_name_end
872
+ );
873
+
874
+ token_T* tag_name_token = create_synthetic_token(allocator, "script", TOKEN_IDENTIFIER, tag_name_start, tag_name_end);
875
+
876
+ char* source_value = NULL;
877
+ bool source_is_string = (source_argument->type == PM_STRING_NODE);
878
+
879
+ if (source_is_string) {
880
+ pm_string_node_t* string_node = (pm_string_node_t*) source_argument;
881
+ size_t length = pm_string_length(&string_node->unescaped);
882
+ source_value = hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
883
+ } else {
884
+ size_t source_length = source_argument->location.end - source_argument->location.start;
885
+ source_value = hb_allocator_strndup(allocator, (const char*) source_argument->location.start, source_length);
886
+ }
887
+
888
+ position_T source_start, source_end;
889
+ prism_node_location_to_positions(&source_argument->location, parse_context, &source_start, &source_end);
890
+
891
+ size_t source_length = strlen(source_value);
892
+ bool is_url = javascript_include_tag_source_is_url(source_value, source_length);
893
+
894
+ char* source_attribute_value = source_value;
895
+ if (source_is_string && !is_url) {
896
+ size_t quoted_length = source_length + 2;
897
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
898
+ quoted_source[0] = '"';
899
+ memcpy(quoted_source + 1, source_value, source_length);
900
+ quoted_source[quoted_length - 1] = '"';
901
+ quoted_source[quoted_length] = '\0';
902
+
903
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, path_options, allocator);
904
+ hb_allocator_dealloc(allocator, quoted_source);
905
+ }
906
+
907
+ hb_array_T* attributes = hb_array_init(hb_array_size(shared_attributes) + 1, allocator);
908
+
909
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
910
+ is_url
911
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
912
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
913
+ if (source_attribute) { hb_array_append(attributes, source_attribute); }
914
+
915
+ for (size_t i = 0; i < hb_array_size(shared_attributes); i++) {
916
+ hb_array_append(attributes, hb_array_get(shared_attributes, i));
917
+ }
918
+
919
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
920
+ hb_allocator_dealloc(allocator, source_value);
921
+
922
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
923
+ erb_node->tag_opening,
924
+ erb_node->content,
925
+ erb_node->tag_closing,
926
+ tag_name_token,
927
+ attributes,
928
+ erb_node->base.location.start,
929
+ erb_node->base.location.end,
930
+ hb_array_init(0, allocator),
931
+ allocator
932
+ );
933
+
934
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
935
+ tag_name_token,
936
+ erb_node->base.location.end,
937
+ erb_node->base.location.end,
938
+ hb_array_init(0, allocator),
939
+ allocator
940
+ );
941
+
942
+ return (AST_NODE_T*) ast_html_element_node_init(
943
+ (AST_NODE_T*) open_tag_node,
944
+ tag_name_token,
945
+ hb_array_init(0, allocator),
946
+ (AST_NODE_T*) virtual_close,
947
+ false,
948
+ parse_context->matched_handler->source,
949
+ erb_node->base.location.start,
950
+ erb_node->base.location.end,
951
+ hb_array_init(0, allocator),
952
+ allocator
953
+ );
954
+ }
955
+
956
+ static hb_array_T* transform_javascript_include_tag_multi_source(
957
+ AST_ERB_CONTENT_NODE_T* erb_node,
958
+ analyze_ruby_context_T* context,
959
+ tag_helper_parse_context_T* parse_context
960
+ ) {
961
+ hb_allocator_T* allocator = context->allocator;
962
+ pm_call_node_t* call_node = parse_context->info->call_node;
963
+ size_t source_count = count_javascript_include_tag_sources(call_node);
964
+
965
+ if (source_count == 0) { return NULL; }
966
+
967
+ hb_array_T* shared_attributes = extract_html_attributes_from_call_node(
968
+ call_node,
969
+ parse_context->prism_source,
970
+ parse_context->original_source,
971
+ parse_context->erb_content_offset,
972
+ allocator
973
+ );
974
+ if (!shared_attributes) { shared_attributes = hb_array_init(0, allocator); }
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
+
985
+ hb_array_T* elements = hb_array_init(source_count * 2, allocator);
986
+
987
+ for (size_t i = 0; i < source_count; i++) {
988
+ pm_node_t* source_arg = call_node->arguments->arguments.nodes[i];
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
+ );
997
+
998
+ if (element) {
999
+ if (hb_array_size(elements) > 0) {
1000
+ position_T position = erb_node->base.location.start;
1001
+ AST_HTML_TEXT_NODE_T* newline = ast_html_text_node_init(
1002
+ hb_string_from_c_string("\n"),
1003
+ position,
1004
+ position,
1005
+ hb_array_init(0, allocator),
1006
+ allocator
1007
+ );
1008
+ if (newline) { hb_array_append(elements, (AST_NODE_T*) newline); }
1009
+ }
1010
+
1011
+ hb_array_append(elements, element);
1012
+ }
1013
+ }
1014
+
1015
+ return elements;
1016
+ }
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
+
375
1054
  static AST_NODE_T* transform_erb_block_to_tag_helper(
376
1055
  AST_ERB_BLOCK_NODE_T* block_node,
377
1056
  analyze_ruby_context_T* context,
@@ -392,6 +1071,7 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
392
1071
  );
393
1072
 
394
1073
  hb_array_T* attributes = NULL;
1074
+
395
1075
  if (parse_context->info->call_node) {
396
1076
  attributes = extract_html_attributes_from_call_node(
397
1077
  parse_context->info->call_node,
@@ -402,6 +1082,12 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
402
1082
  );
403
1083
  }
404
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
+
405
1091
  if (detect_link_to(parse_context->info->call_node, &parse_context->parser)
406
1092
  && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
407
1093
  pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
@@ -476,18 +1162,98 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
476
1162
  );
477
1163
 
478
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);
479
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
+ }
1200
+
1201
+ if (tag_name && parser_is_foreign_content_tag(hb_string_from_c_string(tag_name)) && context->source
1202
+ && block_node->body && hb_array_size(block_node->body) > 0) {
1203
+ size_t start_offset = block_node->tag_closing->range.to;
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
+ }
1228
+
1229
+ if (end_offset > start_offset) {
1230
+ position_T body_start = block_node->tag_closing->location.end;
1231
+
1232
+ size_t content_length = end_offset - start_offset;
1233
+ char* raw_copy = hb_allocator_strndup(allocator, context->source + start_offset, content_length);
1234
+
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;
1241
+
1242
+ AST_DOCUMENT_NODE_T* body_document = herb_parse(raw_copy, &body_options, allocator);
1243
+ body = body_document->children;
1244
+ }
1245
+ }
480
1246
 
481
1247
  AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
482
1248
  (AST_NODE_T*) open_tag_node,
483
1249
  tag_name_token,
484
1250
  body,
485
- close_tag,
486
- false,
1251
+ is_void ? NULL : close_tag,
1252
+ is_void,
487
1253
  parse_context->matched_handler->source,
488
1254
  block_node->base.location.start,
489
- block_node->base.location.end,
490
- hb_array_init(0, allocator),
1255
+ element_end,
1256
+ element_errors,
491
1257
  allocator
492
1258
  );
493
1259
 
@@ -536,6 +1302,12 @@ static AST_NODE_T* transform_link_to_helper(
536
1302
  char* content = hb_allocator_strndup(allocator, (const char*) second_arg->location.start, source_length);
537
1303
 
538
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
+
539
1311
  position_T position = prism_location_to_position_with_offset(
540
1312
  &second_arg->location,
541
1313
  parse_context->original_source,
@@ -544,7 +1316,7 @@ static AST_NODE_T* transform_link_to_helper(
544
1316
  );
545
1317
 
546
1318
  AST_RUBY_HTML_ATTRIBUTES_SPLAT_NODE_T* splat_node = ast_ruby_html_attributes_splat_node_init(
547
- hb_string_from_c_string(content),
1319
+ hb_string_from_c_string(hb_buffer_value(&wrapped)),
548
1320
  HB_STRING_EMPTY,
549
1321
  position,
550
1322
  position,
@@ -554,6 +1326,7 @@ static AST_NODE_T* transform_link_to_helper(
554
1326
 
555
1327
  if (splat_node) { hb_array_append(attributes, (AST_NODE_T*) splat_node); }
556
1328
 
1329
+ hb_buffer_free(&wrapped);
557
1330
  hb_allocator_dealloc(allocator, content);
558
1331
  }
559
1332
  }
@@ -719,21 +1492,8 @@ static AST_NODE_T* transform_link_to_helper(
719
1492
  return (AST_NODE_T*) element;
720
1493
  }
721
1494
 
722
- void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
723
- if (!node || !context) { return; }
724
-
725
- hb_array_T* array = NULL;
726
-
727
- switch (node->type) {
728
- case AST_DOCUMENT_NODE: array = ((AST_DOCUMENT_NODE_T*) node)->children; break;
729
- case AST_HTML_ELEMENT_NODE: array = ((AST_HTML_ELEMENT_NODE_T*) node)->body; break;
730
- case AST_HTML_OPEN_TAG_NODE: array = ((AST_HTML_OPEN_TAG_NODE_T*) node)->children; break;
731
- case AST_HTML_ATTRIBUTE_VALUE_NODE: array = ((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children; break;
732
- case AST_ERB_BLOCK_NODE: array = ((AST_ERB_BLOCK_NODE_T*) node)->body; break;
733
- default: return;
734
- }
735
-
736
- if (!array) { return; }
1495
+ void transform_tag_helper_array(hb_array_T* array, analyze_ruby_context_T* context) {
1496
+ if (!array || !context) { return; }
737
1497
 
738
1498
  for (size_t i = 0; i < hb_array_size(array); i++) {
739
1499
  AST_NODE_T* child = hb_array_get(array, i);
@@ -758,6 +1518,7 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
758
1518
 
759
1519
  if (parse_context) {
760
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);
761
1522
  free_tag_helper_parse_context(parse_context);
762
1523
  }
763
1524
 
@@ -787,20 +1548,260 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
787
1548
  parse_tag_helper_content(erb_string, context->source, erb_content_offset, context->allocator);
788
1549
 
789
1550
  if (parse_context) {
790
- if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
1551
+ if (strcmp(parse_context->matched_handler->name, "javascript_include_tag") == 0
1552
+ && parse_context->info->call_node
1553
+ && count_javascript_include_tag_sources(parse_context->info->call_node) > 1) {
1554
+ hb_array_T* multi = transform_javascript_include_tag_multi_source(erb_node, context, parse_context);
1555
+
1556
+ if (multi && hb_array_size(multi) > 0) {
1557
+ size_t old_size = hb_array_size(array);
1558
+ size_t multi_size = hb_array_size(multi);
1559
+ hb_array_T* new_array = hb_array_init(old_size - 1 + multi_size, context->allocator);
1560
+
1561
+ for (size_t j = 0; j < old_size; j++) {
1562
+ if (j == i) {
1563
+ for (size_t k = 0; k < multi_size; k++) {
1564
+ hb_array_append(new_array, hb_array_get(multi, k));
1565
+ }
1566
+ } else {
1567
+ hb_array_append(new_array, hb_array_get(array, j));
1568
+ }
1569
+ }
1570
+
1571
+ array->items = new_array->items;
1572
+ array->size = new_array->size;
1573
+ array->capacity = new_array->capacity;
1574
+
1575
+ i += multi_size - 1;
1576
+ }
1577
+ } else if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
791
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
+ }
792
1614
  } else {
793
1615
  replacement = transform_tag_helper_with_attributes(erb_node, context, parse_context);
794
1616
  }
795
1617
 
1618
+ replacement = wrap_in_conditional_if_needed(replacement, parse_context, context->allocator);
796
1619
  free_tag_helper_parse_context(parse_context);
797
1620
  }
798
1621
 
799
1622
  free(erb_string);
800
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);
801
1765
  }
1766
+ }
1767
+ }
802
1768
 
803
- if (replacement) { hb_array_set(array, i, replacement); }
1769
+ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
1770
+ if (!node || !context) { return; }
1771
+
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;
804
1805
  }
805
1806
  }
806
1807