herb 0.9.1 → 0.9.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a552819ddaf0c50275110d19455609f491dfb3298da4b76e898eb996bc458e4a
4
- data.tar.gz: 835fc30e2530c8c98aa2077d607f061f78f5ae4030e3c3d449bc2ac9796a6ea4
3
+ metadata.gz: 3a1a61c49f642abaffb189b7ff947009cc5c2e808d4eaa5e8103f0086b6b6b78
4
+ data.tar.gz: d26cdc1d962413d92cc6a2800f4c183777e690aa95c7ab918b5ef4f6591e9278
5
5
  SHA512:
6
- metadata.gz: 3b9e9362ffce00e457a3df27e834419f5a8a0351ef313b7b73b0ae7a0f44dd4689dde96586342ed11ea736ac16baff2473fbab23f141b8f51bdc92aef7cc9d44
7
- data.tar.gz: e76c844b40670b773106547cefd1955fb42867f08f49134557946b24f008187a1cdea4c5f010c40e27505c6dd4bdd4c4f5fa1aae16f92ab45f3a2dbff867d082
6
+ metadata.gz: df024e2b48dadab5af063c54e614948dc46efed4aed24f813476ca491164db7b863692a6617f580bf2e9523701a2f76a8c392935a6fb40cb4b269eece5fb1737
7
+ data.tar.gz: cfb04158d09179dc2a5d08b96a3300b1748ba1ec66afb71c5004e1eaabf1c4cfcfc8b1296d0fb613152eafd0927c400df43622463d5850b341ddfcb6aa03fae4
@@ -10,9 +10,6 @@ module Herb
10
10
 
11
11
  @engine = engine
12
12
  @escape = options.fetch(:escape) { options.fetch(:escape_html, false) }
13
- @attrfunc = options.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
14
- @jsfunc = options.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
15
- @cssfunc = options.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
16
13
  @tokens = [] #: Array[untyped]
17
14
  @element_stack = [] #: Array[String]
18
15
  @context_stack = [:html_content]
@@ -28,26 +25,16 @@ module Herb
28
25
  @engine.send(:add_text, value)
29
26
  when :code
30
27
  @engine.send(:add_code, value)
31
- when :expr
32
- if [:attribute_value, :script_content, :style_content].include?(context)
33
- add_context_aware_expression(value, context)
34
- else
35
- indicator = @escape ? "==" : "="
36
- @engine.send(:add_expression, indicator, value)
37
- end
38
- when :expr_escaped
39
- if [:attribute_value, :script_content, :style_content].include?(context)
40
- add_context_aware_expression(value, context)
28
+ when :expr, :expr_escaped
29
+ indicator = indicator_for(type)
30
+
31
+ if context_aware_context?(context)
32
+ @engine.send(:add_context_aware_expression, indicator, value, context)
41
33
  else
42
- indicator = @escape ? "=" : "=="
43
34
  @engine.send(:add_expression, indicator, value)
44
35
  end
45
- when :expr_block
46
- indicator = @escape ? "==" : "="
47
- @engine.send(:add_expression_block, indicator, value)
48
- when :expr_block_escaped
49
- indicator = @escape ? "=" : "=="
50
- @engine.send(:add_expression_block, indicator, value)
36
+ when :expr_block, :expr_block_escaped
37
+ @engine.send(:add_expression_block, indicator_for(type), value)
51
38
  when :expr_block_end
52
39
  @engine.send(:add_expression_block_end, value, escaped: escaped)
53
40
  end
@@ -342,27 +329,6 @@ module Herb
342
329
  @context_stack.pop
343
330
  end
344
331
 
345
- def add_context_aware_expression(code, context)
346
- closing = code.include?("#") ? "\n))" : "))"
347
-
348
- case context
349
- when :attribute_value
350
- @engine.send(:with_buffer) {
351
- @engine.src << " << #{@attrfunc}((" << code << closing
352
- }
353
- when :script_content
354
- @engine.send(:with_buffer) {
355
- @engine.src << " << #{@jsfunc}((" << code << closing
356
- }
357
- when :style_content
358
- @engine.send(:with_buffer) {
359
- @engine.src << " << #{@cssfunc}((" << code << closing
360
- }
361
- else
362
- @engine.send(:add_expression_result_escaped, code)
363
- end
364
- end
365
-
366
332
  def process_erb_tag(node, skip_comment_check: false)
367
333
  opening = node.tag_opening.value
368
334
 
@@ -503,6 +469,16 @@ module Herb
503
469
  @trim_next_whitespace = true if has_right_trim
504
470
  end
505
471
 
472
+ def indicator_for(type)
473
+ escaped = [:expr_escaped, :expr_block_escaped].include?(type)
474
+
475
+ escaped ^ @escape ? "==" : "="
476
+ end
477
+
478
+ def context_aware_context?(context)
479
+ [:attribute_value, :script_content, :style_content].include?(context)
480
+ end
481
+
506
482
  def should_escape_output?(opening)
507
483
  is_double_equals = opening == "<%=="
508
484
  is_double_equals ? !@escape : @escape
data/lib/herb/engine.rb CHANGED
@@ -55,6 +55,9 @@ module Herb
55
55
  @bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
56
56
  @escape = properties.fetch(:escape) { properties.fetch(:escape_html, false) }
57
57
  @escapefunc = properties.fetch(:escapefunc, @escape ? "__herb.h" : "::Herb::Engine.h")
58
+ @attrfunc = properties.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
59
+ @jsfunc = properties.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
60
+ @cssfunc = properties.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
58
61
  @src = properties[:src] || String.new
59
62
  @chain_appends = properties[:chain_appends]
60
63
  @buffer_on_stack = false
@@ -244,6 +247,24 @@ module Herb
244
247
  end
245
248
  end
246
249
 
250
+ def add_context_aware_expression(indicator, code, context)
251
+ escapefunc = context_escape_function(context)
252
+
253
+ if escapefunc.nil?
254
+ add_expression(indicator, code)
255
+ else
256
+ with_buffer { @src << " << #{escapefunc}((" << code << trailing_newline(code) << "))" }
257
+ end
258
+ end
259
+
260
+ def context_escape_function(context)
261
+ case context
262
+ when :attribute_value then @attrfunc
263
+ when :script_content then @jsfunc
264
+ when :style_content then @cssfunc
265
+ end
266
+ end
267
+
247
268
  def add_expression_result(code)
248
269
  with_buffer {
249
270
  @src << " << (" << code << trailing_newline(code) << ").to_s"
data/lib/herb/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Herb
5
- VERSION = "0.9.1"
5
+ VERSION = "0.9.2"
6
6
  end
@@ -89,8 +89,6 @@ module Herb
89
89
 
90
90
  def pop_context: () -> untyped
91
91
 
92
- def add_context_aware_expression: (untyped code, untyped context) -> untyped
93
-
94
92
  def process_erb_tag: (untyped node, ?skip_comment_check: untyped) -> untyped
95
93
 
96
94
  def add_text: (untyped text) -> untyped
@@ -119,6 +117,10 @@ module Herb
119
117
 
120
118
  def process_erb_output: (untyped node, untyped opening, untyped code) -> untyped
121
119
 
120
+ def indicator_for: (untyped type) -> untyped
121
+
122
+ def context_aware_context?: (untyped context) -> untyped
123
+
122
124
  def should_escape_output?: (untyped opening) -> untyped
123
125
 
124
126
  def add_expression_with_escaping: (untyped code, untyped should_escape) -> untyped
data/sig/herb/engine.rbs CHANGED
@@ -55,6 +55,10 @@ module Herb
55
55
 
56
56
  def add_expression: (untyped indicator, untyped code) -> untyped
57
57
 
58
+ def add_context_aware_expression: (untyped indicator, untyped code, untyped context) -> untyped
59
+
60
+ def context_escape_function: (untyped context) -> untyped
61
+
58
62
  def add_expression_result: (untyped code) -> untyped
59
63
 
60
64
  def add_expression_result_escaped: (untyped code) -> untyped
@@ -1,5 +1,6 @@
1
1
  #include "../../include/analyze/action_view/attribute_extraction_helpers.h"
2
2
  #include "../../include/analyze/action_view/tag_helper_node_builders.h"
3
+ #include "../../include/html_util.h"
3
4
  #include "../../include/util.h"
4
5
  #include "../../include/util/hb_allocator.h"
5
6
  #include "../../include/util/hb_array.h"
@@ -58,6 +59,14 @@ static AST_HTML_ATTRIBUTE_NODE_T* create_attribute_from_value(
58
59
  hb_allocator_dealloc(allocator, value_string);
59
60
 
60
61
  return attribute;
62
+ } else if (value_node->type == PM_TRUE_NODE) {
63
+ if (is_boolean_attribute(hb_string((char*) name_string))) {
64
+ return create_html_attribute_node(name_string, NULL, start_position, end_position, allocator);
65
+ }
66
+ return create_html_attribute_node(name_string, "true", start_position, end_position, allocator);
67
+ } else if (value_node->type == PM_FALSE_NODE) {
68
+ if (is_boolean_attribute(hb_string((char*) name_string))) { return NULL; }
69
+ return create_html_attribute_node(name_string, "false", start_position, end_position, allocator);
61
70
  } else if (value_node->type == PM_INTERPOLATED_STRING_NODE) {
62
71
  return create_html_attribute_with_interpolated_value(
63
72
  name_string,
@@ -0,0 +1,92 @@
1
+ #include "../../include/analyze/action_view/tag_helper_handler.h"
2
+
3
+ #include <prism.h>
4
+ #include <stdbool.h>
5
+ #include <stdlib.h>
6
+ #include <string.h>
7
+
8
+ bool detect_javascript_include_tag(pm_call_node_t* call_node, pm_parser_t* parser) {
9
+ if (!call_node || !call_node->name) { return false; }
10
+
11
+ pm_constant_t* constant = pm_constant_pool_id_to_constant(&parser->constant_pool, call_node->name);
12
+ return constant && constant->length == 22
13
+ && strncmp((const char*) constant->start, "javascript_include_tag", 22) == 0;
14
+ }
15
+
16
+ char* extract_javascript_include_tag_name(pm_call_node_t* call_node, pm_parser_t* parser, hb_allocator_T* allocator) {
17
+ (void) call_node;
18
+ (void) parser;
19
+
20
+ return hb_allocator_strdup(allocator, "script");
21
+ }
22
+
23
+ char* extract_javascript_include_tag_content(
24
+ pm_call_node_t* call_node,
25
+ pm_parser_t* parser,
26
+ hb_allocator_T* allocator
27
+ ) {
28
+ (void) call_node;
29
+ (void) parser;
30
+ (void) allocator;
31
+
32
+ return NULL;
33
+ }
34
+
35
+ char* extract_javascript_include_tag_src(pm_call_node_t* call_node, pm_parser_t* parser, hb_allocator_T* allocator) {
36
+ (void) parser;
37
+
38
+ if (!call_node || !call_node->arguments) { return NULL; }
39
+
40
+ pm_arguments_node_t* arguments = call_node->arguments;
41
+ if (!arguments->arguments.size) { return NULL; }
42
+
43
+ pm_node_t* first_argument = arguments->arguments.nodes[0];
44
+
45
+ if (first_argument->type == PM_STRING_NODE) {
46
+ pm_string_node_t* string_node = (pm_string_node_t*) first_argument;
47
+ size_t length = pm_string_length(&string_node->unescaped);
48
+
49
+ return hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
50
+ }
51
+
52
+ size_t source_length = first_argument->location.end - first_argument->location.start;
53
+ return hb_allocator_strndup(allocator, (const char*) first_argument->location.start, source_length);
54
+ }
55
+
56
+ bool javascript_include_tag_source_is_url(const char* source, size_t length) {
57
+ if (!source || length == 0) { return false; }
58
+
59
+ if (length >= 2 && source[0] == '/' && source[1] == '/') { return true; }
60
+ if (strstr(source, "://") != NULL) { return true; }
61
+
62
+ return false;
63
+ }
64
+
65
+ char* wrap_in_javascript_path(const char* source, size_t source_length, hb_allocator_T* allocator) {
66
+ const char* prefix = "javascript_path(";
67
+ const char* suffix = ")";
68
+ size_t prefix_length = strlen(prefix);
69
+ size_t suffix_length = strlen(suffix);
70
+ size_t total_length = prefix_length + source_length + suffix_length;
71
+ char* result = hb_allocator_alloc(allocator, total_length + 1);
72
+
73
+ memcpy(result, prefix, prefix_length);
74
+ memcpy(result + prefix_length, source, source_length);
75
+ memcpy(result + prefix_length + source_length, suffix, suffix_length);
76
+ result[total_length] = '\0';
77
+
78
+ return result;
79
+ }
80
+
81
+ bool javascript_include_tag_supports_block(void) {
82
+ return false;
83
+ }
84
+
85
+ const tag_helper_handler_T javascript_include_tag_handler = {
86
+ .name = "javascript_include_tag",
87
+ .source = HB_STRING_LITERAL("ActionView::Helpers::AssetTagHelper#javascript_include_tag"),
88
+ .detect = detect_javascript_include_tag,
89
+ .extract_tag_name = extract_javascript_include_tag_name,
90
+ .extract_content = extract_javascript_include_tag_content,
91
+ .supports_block = javascript_include_tag_supports_block
92
+ };
@@ -0,0 +1,55 @@
1
+ #include "../../include/analyze/action_view/tag_helper_handler.h"
2
+
3
+ #include <prism.h>
4
+ #include <stdbool.h>
5
+ #include <stdlib.h>
6
+ #include <string.h>
7
+
8
+ bool detect_javascript_tag(pm_call_node_t* call_node, pm_parser_t* parser) {
9
+ if (!call_node || !call_node->name) { return false; }
10
+
11
+ pm_constant_t* constant = pm_constant_pool_id_to_constant(&parser->constant_pool, call_node->name);
12
+ return constant && constant->length == 14 && strncmp((const char*) constant->start, "javascript_tag", 14) == 0;
13
+ }
14
+
15
+ char* extract_javascript_tag_name(pm_call_node_t* call_node, pm_parser_t* parser, hb_allocator_T* allocator) {
16
+ (void) call_node;
17
+ (void) parser;
18
+
19
+ return hb_allocator_strdup(allocator, "script");
20
+ }
21
+
22
+ char* extract_javascript_tag_content(pm_call_node_t* call_node, pm_parser_t* parser, hb_allocator_T* allocator) {
23
+ (void) parser;
24
+
25
+ if (!call_node || !call_node->arguments) { return NULL; }
26
+
27
+ pm_arguments_node_t* arguments = call_node->arguments;
28
+ if (!arguments->arguments.size) { return NULL; }
29
+
30
+ pm_node_t* first_argument = arguments->arguments.nodes[0];
31
+
32
+ if (first_argument->type == PM_KEYWORD_HASH_NODE) { return NULL; }
33
+
34
+ if (first_argument->type == PM_STRING_NODE) {
35
+ pm_string_node_t* string_node = (pm_string_node_t*) first_argument;
36
+ size_t length = pm_string_length(&string_node->unescaped);
37
+ return hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
38
+ }
39
+
40
+ size_t source_length = first_argument->location.end - first_argument->location.start;
41
+ return hb_allocator_strndup(allocator, (const char*) first_argument->location.start, source_length);
42
+ }
43
+
44
+ bool javascript_tag_supports_block(void) {
45
+ return true;
46
+ }
47
+
48
+ const tag_helper_handler_T javascript_tag_handler = {
49
+ .name = "javascript_tag",
50
+ .source = HB_STRING_LITERAL("ActionView::Helpers::JavaScriptHelper#javascript_tag"),
51
+ .detect = detect_javascript_tag,
52
+ .extract_tag_name = extract_javascript_tag_name,
53
+ .extract_content = extract_javascript_tag_content,
54
+ .supports_block = javascript_tag_supports_block
55
+ };
@@ -8,8 +8,10 @@ extern const tag_helper_handler_T content_tag_handler;
8
8
  extern const tag_helper_handler_T tag_dot_handler;
9
9
  extern const tag_helper_handler_T link_to_handler;
10
10
  extern const tag_helper_handler_T turbo_frame_tag_handler;
11
+ extern const tag_helper_handler_T javascript_tag_handler;
12
+ extern const tag_helper_handler_T javascript_include_tag_handler;
11
13
 
12
- static size_t handlers_count = 4;
14
+ static size_t handlers_count = 6;
13
15
 
14
16
  tag_helper_info_T* tag_helper_info_init(hb_allocator_T* allocator) {
15
17
  tag_helper_info_T* info = hb_allocator_alloc(allocator, sizeof(tag_helper_info_T));
@@ -41,7 +43,7 @@ void tag_helper_info_free(tag_helper_info_T** info) {
41
43
  }
42
44
 
43
45
  tag_helper_handler_T* get_tag_helper_handlers(void) {
44
- static tag_helper_handler_T static_handlers[4];
46
+ static tag_helper_handler_T static_handlers[6];
45
47
  static bool initialized = false;
46
48
 
47
49
  if (!initialized) {
@@ -49,6 +51,8 @@ tag_helper_handler_T* get_tag_helper_handlers(void) {
49
51
  static_handlers[1] = tag_dot_handler;
50
52
  static_handlers[2] = link_to_handler;
51
53
  static_handlers[3] = turbo_frame_tag_handler;
54
+ static_handlers[4] = javascript_tag_handler;
55
+ static_handlers[5] = javascript_include_tag_handler;
52
56
  initialized = true;
53
57
  }
54
58
 
@@ -88,7 +88,8 @@ AST_HTML_ATTRIBUTE_NODE_T* create_html_attribute_node(
88
88
  AST_HTML_ATTRIBUTE_NAME_NODE_T* name_node =
89
89
  create_attribute_name_node(name_string, start_position, end_position, allocator);
90
90
 
91
- token_T* equals_token = create_synthetic_token(allocator, "=", TOKEN_EQUALS, start_position, end_position);
91
+ token_T* equals_token =
92
+ value_string ? create_synthetic_token(allocator, "=", TOKEN_EQUALS, start_position, end_position) : NULL;
92
93
  AST_HTML_ATTRIBUTE_VALUE_NODE_T* value_node = NULL;
93
94
 
94
95
  if (value_string) {
@@ -210,8 +211,20 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* create_interpolated_attribute_value(
210
211
  }
211
212
  }
212
213
  } else if (part->type == PM_EMBEDDED_STATEMENTS_NODE) {
213
- size_t ruby_length = part->location.end - part->location.start;
214
- char* ruby_content = hb_allocator_strndup(allocator, (const char*) part->location.start, ruby_length);
214
+ pm_embedded_statements_node_t* embedded = (pm_embedded_statements_node_t*) part;
215
+ const uint8_t* content_start;
216
+ const uint8_t* content_end;
217
+
218
+ if (embedded->statements) {
219
+ content_start = embedded->statements->base.location.start;
220
+ content_end = embedded->statements->base.location.end;
221
+ } else {
222
+ content_start = part->location.start;
223
+ content_end = part->location.end;
224
+ }
225
+
226
+ size_t ruby_length = content_end - content_start;
227
+ char* ruby_content = hb_allocator_strndup(allocator, (const char*) content_start, ruby_length);
215
228
 
216
229
  if (ruby_content) {
217
230
  AST_RUBY_LITERAL_NODE_T* ruby_node = ast_ruby_literal_node_init(
@@ -5,6 +5,7 @@
5
5
  #include "../../include/analyze/analyze.h"
6
6
  #include "../../include/ast_nodes.h"
7
7
  #include "../../include/html_util.h"
8
+ #include "../../include/parser_helpers.h"
8
9
  #include "../../include/position.h"
9
10
  #include "../../include/util/hb_allocator.h"
10
11
  #include "../../include/util/hb_array.h"
@@ -22,6 +23,10 @@ extern char* wrap_in_url_for(const char*, size_t, hb_allocator_T*);
22
23
  extern char* extract_link_to_href(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
23
24
  extern bool detect_turbo_frame_tag(pm_call_node_t*, pm_parser_t*);
24
25
  extern char* extract_turbo_frame_tag_id(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
26
+ extern bool detect_javascript_include_tag(pm_call_node_t*, pm_parser_t*);
27
+ extern char* extract_javascript_include_tag_src(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
28
+ extern char* wrap_in_javascript_path(const char*, size_t, hb_allocator_T*);
29
+ extern bool javascript_include_tag_source_is_url(const char*, size_t);
25
30
 
26
31
  typedef struct {
27
32
  pm_parser_t parser;
@@ -310,6 +315,47 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
310
315
  }
311
316
  }
312
317
 
318
+ if (detect_javascript_include_tag(parse_context->info->call_node, &parse_context->parser)
319
+ && parse_context->info->call_node->arguments && parse_context->info->call_node->arguments->arguments.size >= 1) {
320
+ char* source_value =
321
+ extract_javascript_include_tag_src(parse_context->info->call_node, &parse_context->parser, allocator);
322
+
323
+ if (source_value) {
324
+ if (!attributes) { attributes = hb_array_init(4, allocator); }
325
+
326
+ pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
327
+ position_T source_start, source_end;
328
+ prism_node_location_to_positions(&first_argument->location, parse_context, &source_start, &source_end);
329
+ bool source_is_string = (first_argument->type == PM_STRING_NODE);
330
+
331
+ size_t source_length = strlen(source_value);
332
+ bool is_url = javascript_include_tag_source_is_url(source_value, source_length);
333
+
334
+ char* source_attribute_value = source_value;
335
+ if (source_is_string && !is_url) {
336
+ size_t quoted_length = source_length + 2;
337
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
338
+ quoted_source[0] = '"';
339
+ memcpy(quoted_source + 1, source_value, source_length);
340
+ quoted_source[quoted_length - 1] = '"';
341
+ quoted_source[quoted_length] = '\0';
342
+
343
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, allocator);
344
+ hb_allocator_dealloc(allocator, quoted_source);
345
+ }
346
+
347
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
348
+ is_url
349
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
350
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
351
+
352
+ if (source_attribute) { attributes = prepend_attribute(attributes, (AST_NODE_T*) source_attribute, allocator); }
353
+
354
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
355
+ hb_allocator_dealloc(allocator, source_value);
356
+ }
357
+ }
358
+
313
359
  token_T* tag_name_token =
314
360
  tag_name ? create_synthetic_token(allocator, tag_name, TOKEN_IDENTIFIER, tag_name_start, tag_name_end) : NULL;
315
361
 
@@ -372,6 +418,166 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
372
418
  return (AST_NODE_T*) element;
373
419
  }
374
420
 
421
+ static size_t count_javascript_include_tag_sources(pm_call_node_t* call_node) {
422
+ if (!call_node || !call_node->arguments) { return 0; }
423
+
424
+ size_t count = 0;
425
+ pm_arguments_node_t* arguments = call_node->arguments;
426
+
427
+ for (size_t i = 0; i < arguments->arguments.size; i++) {
428
+ pm_node_t* arg = arguments->arguments.nodes[i];
429
+ if (arg->type == PM_KEYWORD_HASH_NODE) { break; }
430
+ count++;
431
+ }
432
+
433
+ return count;
434
+ }
435
+
436
+ static AST_NODE_T* create_javascript_include_tag_element(
437
+ AST_ERB_CONTENT_NODE_T* erb_node,
438
+ tag_helper_parse_context_T* parse_context,
439
+ pm_node_t* source_argument,
440
+ hb_array_T* shared_attributes,
441
+ hb_allocator_T* allocator
442
+ ) {
443
+ position_T tag_name_start, tag_name_end;
444
+ calculate_tag_name_positions(
445
+ parse_context,
446
+ erb_node->base.location.start,
447
+ erb_node->base.location.end,
448
+ &tag_name_start,
449
+ &tag_name_end
450
+ );
451
+
452
+ token_T* tag_name_token = create_synthetic_token(allocator, "script", TOKEN_IDENTIFIER, tag_name_start, tag_name_end);
453
+
454
+ char* source_value = NULL;
455
+ bool source_is_string = (source_argument->type == PM_STRING_NODE);
456
+
457
+ if (source_is_string) {
458
+ pm_string_node_t* string_node = (pm_string_node_t*) source_argument;
459
+ size_t length = pm_string_length(&string_node->unescaped);
460
+ source_value = hb_allocator_strndup(allocator, (const char*) pm_string_source(&string_node->unescaped), length);
461
+ } else {
462
+ size_t source_length = source_argument->location.end - source_argument->location.start;
463
+ source_value = hb_allocator_strndup(allocator, (const char*) source_argument->location.start, source_length);
464
+ }
465
+
466
+ position_T source_start, source_end;
467
+ prism_node_location_to_positions(&source_argument->location, parse_context, &source_start, &source_end);
468
+
469
+ size_t source_length = strlen(source_value);
470
+ bool is_url = javascript_include_tag_source_is_url(source_value, source_length);
471
+
472
+ char* source_attribute_value = source_value;
473
+ if (source_is_string && !is_url) {
474
+ size_t quoted_length = source_length + 2;
475
+ char* quoted_source = hb_allocator_alloc(allocator, quoted_length + 1);
476
+ quoted_source[0] = '"';
477
+ memcpy(quoted_source + 1, source_value, source_length);
478
+ quoted_source[quoted_length - 1] = '"';
479
+ quoted_source[quoted_length] = '\0';
480
+
481
+ source_attribute_value = wrap_in_javascript_path(quoted_source, quoted_length, allocator);
482
+ hb_allocator_dealloc(allocator, quoted_source);
483
+ }
484
+
485
+ hb_array_T* attributes = hb_array_init(hb_array_size(shared_attributes) + 1, allocator);
486
+
487
+ AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
488
+ is_url
489
+ ? create_html_attribute_node("src", source_attribute_value, source_start, source_end, allocator)
490
+ : create_html_attribute_with_ruby_literal("src", source_attribute_value, source_start, source_end, allocator);
491
+ if (source_attribute) { hb_array_append(attributes, source_attribute); }
492
+
493
+ for (size_t i = 0; i < hb_array_size(shared_attributes); i++) {
494
+ hb_array_append(attributes, hb_array_get(shared_attributes, i));
495
+ }
496
+
497
+ if (source_attribute_value != source_value) { hb_allocator_dealloc(allocator, source_attribute_value); }
498
+ hb_allocator_dealloc(allocator, source_value);
499
+
500
+ AST_ERB_OPEN_TAG_NODE_T* open_tag_node = ast_erb_open_tag_node_init(
501
+ erb_node->tag_opening,
502
+ erb_node->content,
503
+ erb_node->tag_closing,
504
+ tag_name_token,
505
+ attributes,
506
+ erb_node->base.location.start,
507
+ erb_node->base.location.end,
508
+ hb_array_init(0, allocator),
509
+ allocator
510
+ );
511
+
512
+ AST_HTML_VIRTUAL_CLOSE_TAG_NODE_T* virtual_close = ast_html_virtual_close_tag_node_init(
513
+ tag_name_token,
514
+ erb_node->base.location.end,
515
+ erb_node->base.location.end,
516
+ hb_array_init(0, allocator),
517
+ allocator
518
+ );
519
+
520
+ return (AST_NODE_T*) ast_html_element_node_init(
521
+ (AST_NODE_T*) open_tag_node,
522
+ tag_name_token,
523
+ hb_array_init(0, allocator),
524
+ (AST_NODE_T*) virtual_close,
525
+ false,
526
+ parse_context->matched_handler->source,
527
+ erb_node->base.location.start,
528
+ erb_node->base.location.end,
529
+ hb_array_init(0, allocator),
530
+ allocator
531
+ );
532
+ }
533
+
534
+ static hb_array_T* transform_javascript_include_tag_multi_source(
535
+ AST_ERB_CONTENT_NODE_T* erb_node,
536
+ analyze_ruby_context_T* context,
537
+ tag_helper_parse_context_T* parse_context
538
+ ) {
539
+ hb_allocator_T* allocator = context->allocator;
540
+ pm_call_node_t* call_node = parse_context->info->call_node;
541
+ size_t source_count = count_javascript_include_tag_sources(call_node);
542
+
543
+ if (source_count == 0) { return NULL; }
544
+
545
+ hb_array_T* shared_attributes = extract_html_attributes_from_call_node(
546
+ call_node,
547
+ parse_context->prism_source,
548
+ parse_context->original_source,
549
+ parse_context->erb_content_offset,
550
+ allocator
551
+ );
552
+ if (!shared_attributes) { shared_attributes = hb_array_init(0, allocator); }
553
+
554
+ hb_array_T* elements = hb_array_init(source_count * 2, allocator);
555
+
556
+ for (size_t i = 0; i < source_count; i++) {
557
+ pm_node_t* source_arg = call_node->arguments->arguments.nodes[i];
558
+ AST_NODE_T* element =
559
+ create_javascript_include_tag_element(erb_node, parse_context, source_arg, shared_attributes, allocator);
560
+
561
+ if (element) {
562
+ if (hb_array_size(elements) > 0) {
563
+ position_T position = erb_node->base.location.start;
564
+ AST_HTML_TEXT_NODE_T* newline = ast_html_text_node_init(
565
+ hb_string_from_c_string("\n"),
566
+ position,
567
+ position,
568
+ hb_array_init(0, allocator),
569
+ allocator
570
+ );
571
+ if (newline) { hb_array_append(elements, (AST_NODE_T*) newline); }
572
+ }
573
+
574
+ hb_array_append(elements, element);
575
+ }
576
+ }
577
+
578
+ return elements;
579
+ }
580
+
375
581
  static AST_NODE_T* transform_erb_block_to_tag_helper(
376
582
  AST_ERB_BLOCK_NODE_T* block_node,
377
583
  analyze_ruby_context_T* context,
@@ -478,6 +684,27 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
478
684
  hb_array_T* body = block_node->body ? block_node->body : hb_array_init(0, allocator);
479
685
  AST_NODE_T* close_tag = (AST_NODE_T*) block_node->end_node;
480
686
 
687
+ if (tag_name && parser_is_foreign_content_tag(hb_string_from_c_string(tag_name)) && context->source
688
+ && block_node->body && hb_array_size(block_node->body) > 0) {
689
+ size_t start_offset = block_node->tag_closing->range.to;
690
+ size_t end_offset = block_node->end_node->tag_opening->range.from;
691
+
692
+ if (end_offset > start_offset) {
693
+ position_T body_start = block_node->tag_closing->location.end;
694
+ position_T body_end = block_node->end_node->tag_opening->location.start;
695
+
696
+ size_t content_length = end_offset - start_offset;
697
+ char* raw_copy = hb_allocator_strndup(allocator, context->source + start_offset, content_length);
698
+ hb_string_T raw_content = { .data = raw_copy, .length = content_length };
699
+
700
+ AST_LITERAL_NODE_T* literal_node =
701
+ ast_literal_node_init(raw_content, body_start, body_end, hb_array_init(0, allocator), allocator);
702
+
703
+ body = hb_array_init(1, allocator);
704
+ hb_array_append(body, literal_node);
705
+ }
706
+ }
707
+
481
708
  AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init(
482
709
  (AST_NODE_T*) open_tag_node,
483
710
  tag_name_token,
@@ -787,7 +1014,33 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
787
1014
  parse_tag_helper_content(erb_string, context->source, erb_content_offset, context->allocator);
788
1015
 
789
1016
  if (parse_context) {
790
- if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
1017
+ if (strcmp(parse_context->matched_handler->name, "javascript_include_tag") == 0
1018
+ && parse_context->info->call_node
1019
+ && count_javascript_include_tag_sources(parse_context->info->call_node) > 1) {
1020
+ hb_array_T* multi = transform_javascript_include_tag_multi_source(erb_node, context, parse_context);
1021
+
1022
+ if (multi && hb_array_size(multi) > 0) {
1023
+ size_t old_size = hb_array_size(array);
1024
+ size_t multi_size = hb_array_size(multi);
1025
+ hb_array_T* new_array = hb_array_init(old_size - 1 + multi_size, context->allocator);
1026
+
1027
+ for (size_t j = 0; j < old_size; j++) {
1028
+ if (j == i) {
1029
+ for (size_t k = 0; k < multi_size; k++) {
1030
+ hb_array_append(new_array, hb_array_get(multi, k));
1031
+ }
1032
+ } else {
1033
+ hb_array_append(new_array, hb_array_get(array, j));
1034
+ }
1035
+ }
1036
+
1037
+ array->items = new_array->items;
1038
+ array->size = new_array->size;
1039
+ array->capacity = new_array->capacity;
1040
+
1041
+ i += multi_size - 1;
1042
+ }
1043
+ } else if (strcmp(parse_context->matched_handler->name, "link_to") == 0) {
791
1044
  replacement = transform_link_to_helper(erb_node, context, parse_context);
792
1045
  } else {
793
1046
  replacement = transform_tag_helper_with_attributes(erb_node, context, parse_context);
data/src/html_util.c CHANGED
@@ -27,6 +27,46 @@ static hb_string_T void_tags[] = HB_STRING_LIST(
27
27
  "wbr"
28
28
  );
29
29
 
30
+ // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
31
+ static hb_string_T boolean_attributes[] = HB_STRING_LIST(
32
+ "allowfullscreen",
33
+ "async",
34
+ "autofocus",
35
+ "autoplay",
36
+ "checked",
37
+ "compact",
38
+ "controls",
39
+ "declare",
40
+ "default",
41
+ "defer",
42
+ "disabled",
43
+ "formnovalidate",
44
+ "hidden",
45
+ "inert",
46
+ "ismap",
47
+ "itemscope",
48
+ "loop",
49
+ "multiple",
50
+ "muted",
51
+ "nomodule",
52
+ "nohref",
53
+ "noresize",
54
+ "noshade",
55
+ "novalidate",
56
+ "nowrap",
57
+ "open",
58
+ "playsinline",
59
+ "readonly",
60
+ "required",
61
+ "reversed",
62
+ "scoped",
63
+ "seamless",
64
+ "selected",
65
+ "sortable",
66
+ "truespeed",
67
+ "typemustmatch"
68
+ );
69
+
30
70
  // https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
31
71
  static hb_string_T optional_end_tags[] = HB_STRING_LIST(
32
72
  "li",
@@ -112,6 +152,16 @@ bool is_void_element(hb_string_T tag_name) {
112
152
  return false;
113
153
  }
114
154
 
155
+ bool is_boolean_attribute(hb_string_T attribute_name) {
156
+ if (hb_string_is_empty(attribute_name)) { return false; }
157
+
158
+ for (size_t i = 0; i < sizeof(boolean_attributes) / sizeof(boolean_attributes[0]); i++) {
159
+ if (hb_string_equals_case_insensitive(attribute_name, boolean_attributes[i])) { return true; }
160
+ }
161
+
162
+ return false;
163
+ }
164
+
115
165
  bool has_optional_end_tag(hb_string_T tag_name) {
116
166
  if (hb_string_is_empty(tag_name)) { return false; }
117
167
 
@@ -7,6 +7,7 @@
7
7
  struct hb_allocator;
8
8
 
9
9
  bool is_void_element(hb_string_T tag_name);
10
+ bool is_boolean_attribute(hb_string_T attribute_name);
10
11
  bool has_optional_end_tag(hb_string_T tag_name);
11
12
  bool should_implicitly_close(hb_string_T open_tag_name, hb_string_T next_tag_name);
12
13
  bool parent_closes_element(hb_string_T open_tag_name, hb_string_T parent_close_tag_name);
@@ -1,6 +1,6 @@
1
1
  #ifndef HERB_VERSION_H
2
2
  #define HERB_VERSION_H
3
3
 
4
- #define HERB_VERSION "0.9.1"
4
+ #define HERB_VERSION "0.9.2"
5
5
 
6
6
  #endif
data/src/util/hb_arena.c CHANGED
@@ -70,16 +70,12 @@ static bool hb_arena_append_page(hb_arena_T* allocator, size_t page_size) {
70
70
  page->position = 0;
71
71
 
72
72
  if (allocator->head == NULL) {
73
+ assert(allocator->tail == NULL);
74
+
73
75
  allocator->head = page;
74
76
  allocator->tail = page;
75
77
  } else {
76
- hb_arena_page_T* last = allocator->head;
77
-
78
- while (last->next != NULL) {
79
- last = last->next;
80
- }
81
-
82
- last->next = page;
78
+ allocator->tail->next = page;
83
79
  allocator->tail = page;
84
80
  }
85
81
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: herb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth
@@ -111,6 +111,8 @@ files:
111
111
  - sig/serialized_ast_nodes.rbs
112
112
  - src/analyze/action_view/attribute_extraction_helpers.c
113
113
  - src/analyze/action_view/content_tag.c
114
+ - src/analyze/action_view/javascript_include_tag.c
115
+ - src/analyze/action_view/javascript_tag.c
114
116
  - src/analyze/action_view/link_to.c
115
117
  - src/analyze/action_view/registry.c
116
118
  - src/analyze/action_view/tag.c