herb 0.9.3 → 0.9.4

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: a490ad868e9fa08be317c7343a0553a8bf07234decbb95046d235d64256ed743
4
- data.tar.gz: 2f1312897b63fe33263bf35f023b6d2377bfb9e806d5f640837d9de57b2878bb
3
+ metadata.gz: dad9b36758689e8b12d360b0ab22322e820762b4b065556d90a5d1d2cdd319b0
4
+ data.tar.gz: d298f4e91debfefac4725f7647f671d4eeb571a0d6e52d19b2452eeaf5c4b497
5
5
  SHA512:
6
- metadata.gz: f820a5463221a38d26c92a3a388f137bb2295125641dd7de7102290c85036adfa5c0bdafd3331e049fff6fc218a8c53b094fd5037277276c67121a64e99b7535
7
- data.tar.gz: 6bee2c356c1e18c5670ce9ffcd8193100f0be4dc8d2aba74faaadc6409fe0d534390c34796dcd5aa8c1bfa4c55209cf07b856898a6a66cf654e871d14a869003
6
+ metadata.gz: a935b405301ba5ffc3531ae41157321278d4f6f56a56a15d7e287605493eaf2d4fe9a4a43e753001ea094d462d7972c50691c569ea01c7e2470e8f41cc8d4623
7
+ data.tar.gz: 1157b317060949235da509fb8f7b33f424ade19a1288b3319140c233e6df53afc560042ca4713ee71abfdb00975b673844d0bd16d01259ea2b0fbe91cce43ae8
@@ -5,6 +5,12 @@ module Herb
5
5
  class Compiler < ::Herb::Visitor
6
6
  EXPRESSION_TOKEN_TYPES = [:expr, :expr_escaped, :expr_block, :expr_block_escaped].freeze
7
7
 
8
+ TRAILING_WHITESPACE = /[ \t]+\z/
9
+ TRAILING_INDENTATION = /\n[ \t]+\z/
10
+ TRAILING_INDENTATION_CAPTURE = /\n([ \t]+)\z/
11
+ WHITESPACE_ONLY = /\A[ \t]+\z/
12
+ WHITESPACE_ONLY_CAPTURE = /\A([ \t]+)\z/
13
+
8
14
  attr_reader :tokens
9
15
 
10
16
  def initialize(engine, options = {})
@@ -242,6 +248,8 @@ module Herb
242
248
  def visit_erb_block_node(node)
243
249
  opening = node.tag_opening.value
244
250
 
251
+ check_for_escaped_erb_tag!(opening)
252
+
245
253
  if opening.include?("=")
246
254
  should_escape = should_escape_output?(opening)
247
255
  code = node.content.value.strip
@@ -251,7 +259,9 @@ module Herb
251
259
  else
252
260
  [:expr_block, code, current_context]
253
261
  end
262
+
254
263
  @last_trim_consumed_newline = false
264
+ @trim_next_whitespace = true if right_trim?(node)
255
265
 
256
266
  visit_all(node.body)
257
267
  visit_erb_block_end_node(node.end_node, escaped: should_escape)
@@ -267,9 +277,7 @@ module Herb
267
277
  end
268
278
 
269
279
  def visit_erb_block_end_node(node, escaped: false)
270
- has_left_trim = node.tag_opening.value.start_with?("<%-")
271
-
272
- remove_trailing_whitespace_from_last_token! if has_left_trim
280
+ remove_trailing_whitespace_from_last_token! if left_trim?(node)
273
281
 
274
282
  code = node.content.value.strip
275
283
 
@@ -302,6 +310,15 @@ module Herb
302
310
 
303
311
  private
304
312
 
313
+ def check_for_escaped_erb_tag!(opening)
314
+ return unless opening.start_with?("<%%")
315
+
316
+ raise Herb::Engine::GeneratorTemplateError,
317
+ "This file appears to be a generator template (a template used to generate ERB files) " \
318
+ "rather than a standard ERB template. It contains escaped ERB tags like <%%= %> which " \
319
+ "produce literal ERB output in the generated file."
320
+ end
321
+
305
322
  def current_context
306
323
  @context_stack.last
307
324
  end
@@ -336,10 +353,11 @@ module Herb
336
353
  def process_erb_tag(node, skip_comment_check: false)
337
354
  opening = node.tag_opening.value
338
355
 
356
+ check_for_escaped_erb_tag!(opening)
357
+
339
358
  if !skip_comment_check && erb_comment?(opening)
340
- has_left_trim = opening.start_with?("<%-")
341
359
  follows_newline = leading_space_follows_newline?
342
- remove_trailing_whitespace_from_last_token! if has_left_trim
360
+ remove_trailing_whitespace_from_last_token! if left_trim?(node)
343
361
 
344
362
  if at_line_start?
345
363
  leading_space = extract_and_remove_leading_space!
@@ -485,10 +503,9 @@ module Herb
485
503
  @last_trim_consumed_newline = false
486
504
  end
487
505
 
488
- has_right_trim = node.tag_closing&.value == "-%>"
489
506
  should_escape = should_escape_output?(opening)
490
507
  add_expression_with_escaping(code, should_escape)
491
- @trim_next_whitespace = true if has_right_trim
508
+ @trim_next_whitespace = true if right_trim?(node)
492
509
  end
493
510
 
494
511
  def indicator_for(type)
@@ -521,7 +538,7 @@ module Herb
521
538
  last_value = @tokens.last[1]
522
539
 
523
540
  if last_type == :text
524
- last_value.empty? || last_value.end_with?("\n") || (last_value =~ /\A[ \t]+\z/ && preceding_token_ends_with_newline?) || last_value =~ /\n[ \t]+\z/
541
+ last_value.empty? || last_value.end_with?("\n") || (last_value =~ WHITESPACE_ONLY && preceding_token_ends_with_newline?) || last_value =~ TRAILING_INDENTATION
525
542
  elsif EXPRESSION_TOKEN_TYPES.include?(last_type)
526
543
  @last_trim_consumed_newline
527
544
  else
@@ -540,6 +557,14 @@ module Herb
540
557
  preceding[1].end_with?("\n")
541
558
  end
542
559
 
560
+ def left_trim?(node)
561
+ node.tag_opening.value == "<%-"
562
+ end
563
+
564
+ def right_trim?(node)
565
+ node.tag_closing&.value == "-%>"
566
+ end
567
+
543
568
  def last_text_token
544
569
  return unless @tokens.last && @tokens.last[0] == :text
545
570
 
@@ -552,7 +577,7 @@ module Herb
552
577
 
553
578
  text = token[1]
554
579
 
555
- return Regexp.last_match(1) if text =~ /\n([ \t]+)\z/ || text =~ /\A([ \t]+)\z/
580
+ return Regexp.last_match(1) if text =~ TRAILING_INDENTATION_CAPTURE || text =~ WHITESPACE_ONLY_CAPTURE
556
581
 
557
582
  ""
558
583
  end
@@ -561,7 +586,12 @@ module Herb
561
586
  token = last_text_token
562
587
  return false unless token
563
588
 
564
- token[1].match?(/\n[ \t]+\z/)
589
+ text = token[1]
590
+
591
+ return true if text.match?(TRAILING_INDENTATION)
592
+ return true if @last_trim_consumed_newline && text.match?(WHITESPACE_ONLY)
593
+
594
+ false
565
595
  end
566
596
 
567
597
  def extract_and_remove_leading_space!
@@ -570,9 +600,9 @@ module Herb
570
600
 
571
601
  text = @tokens.last[1]
572
602
 
573
- if text =~ /\n[ \t]+\z/
574
- text.sub!(/[ \t]+\z/, "")
575
- elsif text =~ /\A[ \t]+\z/
603
+ if text =~ TRAILING_INDENTATION
604
+ text.sub!(TRAILING_WHITESPACE, "")
605
+ elsif text =~ WHITESPACE_ONLY
576
606
  text.replace("")
577
607
  end
578
608
 
@@ -582,10 +612,8 @@ module Herb
582
612
  end
583
613
 
584
614
  def apply_trim(node, code)
585
- has_left_trim = node.tag_opening.value.start_with?("<%-")
586
-
587
615
  follows_newline = leading_space_follows_newline?
588
- removed_whitespace = has_left_trim ? remove_trailing_whitespace_from_last_token! : ""
616
+ removed_whitespace = left_trim?(node) ? remove_trailing_whitespace_from_last_token! : ""
589
617
 
590
618
  if at_line_start?
591
619
  leading_space = extract_and_remove_leading_space!
@@ -617,12 +645,12 @@ module Herb
617
645
  return "" unless token
618
646
 
619
647
  text = token[1]
620
- removed = text[/[ \t]+\z/] || ""
648
+ removed = text[TRAILING_WHITESPACE] || ""
621
649
 
622
- if text =~ /\n[ \t]+\z/
623
- text.sub!(/[ \t]+\z/, "")
650
+ if text =~ TRAILING_INDENTATION
651
+ text.sub!(TRAILING_WHITESPACE, "")
624
652
  token[1] = text
625
- elsif text =~ /\A[ \t]+\z/
653
+ elsif text =~ WHITESPACE_ONLY
626
654
  text.replace("")
627
655
  token[1] = text
628
656
  end
data/lib/herb/engine.rb CHANGED
@@ -31,6 +31,9 @@ module Herb
31
31
  class CompilationError < StandardError
32
32
  end
33
33
 
34
+ class GeneratorTemplateError < CompilationError
35
+ end
36
+
34
37
  class InvalidRubyError < CompilationError
35
38
  attr_reader :compiled_source
36
39
 
data/lib/herb/project.rb CHANGED
@@ -71,9 +71,9 @@ module Herb
71
71
  attr_reader :successful, :failed, :timeout, :template_error, :unexpected_error,
72
72
  :strict_parse_error, :analyze_parse_error,
73
73
  :validation_error, :compilation_failed, :strict_compilation_failed,
74
- :invalid_ruby,
74
+ :invalid_ruby, :skipped,
75
75
  :error_outputs, :file_contents, :parse_errors, :compilation_errors,
76
- :file_diagnostics
76
+ :file_diagnostics, :skip_reasons
77
77
 
78
78
  def initialize
79
79
  @successful = []
@@ -87,11 +87,13 @@ module Herb
87
87
  @compilation_failed = []
88
88
  @strict_compilation_failed = []
89
89
  @invalid_ruby = []
90
+ @skipped = []
90
91
  @error_outputs = {}
91
92
  @file_contents = {}
92
93
  @parse_errors = {}
93
94
  @compilation_errors = {}
94
95
  @file_diagnostics = {}
96
+ @skip_reasons = {}
95
97
  end
96
98
 
97
99
  def problem_files
@@ -432,8 +434,7 @@ module Herb
432
434
  nil
433
435
  end
434
436
 
435
- { file_path: file_path, status: :timeout, file_content: file_content,
436
- log: "⏱️ Parsing #{file_path} timed out after 1 second" }
437
+ { file_path: file_path, status: :timeout, file_content: file_content, log: "⏱️ Parsing #{file_path} timed out after 1 second" }
437
438
  rescue StandardError => e
438
439
  file_content ||= begin
439
440
  File.read(file_path)
@@ -441,8 +442,7 @@ module Herb
441
442
  nil
442
443
  end
443
444
 
444
- { file_path: file_path, status: :failed, file_content: file_content,
445
- log: "⚠️ Error processing #{file_path}: #{e.message}" }
445
+ { file_path: file_path, status: :failed, file_content: file_content, log: "⚠️ Error processing #{file_path}: #{e.message}" }
446
446
  ensure
447
447
  [stdout_file, stderr_file].each do |tempfile|
448
448
  next unless tempfile
@@ -488,6 +488,9 @@ module Herb
488
488
  Herb::Engine.new(file_content, filename: file_path, escape: true, validate_ruby: validate_ruby)
489
489
 
490
490
  { status: :successful, log: "✅ Compiled #{file_path} successfully" }
491
+ rescue Herb::Engine::GeneratorTemplateError => e
492
+ { status: :skipped, skip_reason: e.message,
493
+ log: "⊘ Skipping #{file_path}: #{e.message}" }
491
494
  rescue Herb::Engine::InvalidRubyError => e
492
495
  { status: :invalid_ruby, file_content: file_content,
493
496
  compilation_error: { error: e.message, backtrace: e.backtrace&.first(10) || [] },
@@ -545,6 +548,7 @@ module Herb
545
548
  tracker.parse_errors[file_path] = result[:parse_error] if result[:parse_error]
546
549
  tracker.compilation_errors[file_path] = result[:compilation_error] if result[:compilation_error]
547
550
  tracker.file_diagnostics[file_path] = result[:diagnostics] if result[:diagnostics]&.any?
551
+ tracker.skip_reasons[file_path] = result[:skip_reason] if result[:skip_reason]
548
552
  end
549
553
 
550
554
  def print_summary(results, log, duration)
@@ -563,13 +567,18 @@ module Herb
563
567
  puts " #{label("Checked")} #{cyan("#{total} #{pluralize(total, "file")}")}"
564
568
 
565
569
  if total > 1
566
- files_line = if issues.positive?
567
- "#{bold(green("#{passed} clean"))} | #{bold(red("#{issues} with issues"))}"
568
- else
569
- bold(green("#{total} clean"))
570
- end
570
+ files_parts = []
571
571
 
572
- puts " #{label("Files")} #{files_line}"
572
+ if issues.positive?
573
+ files_parts << bold(green("#{passed} clean"))
574
+ files_parts << bold(red("#{issues} with issues"))
575
+ else
576
+ files_parts << bold(green("#{total - results.skipped.count} clean"))
577
+ end
578
+
579
+ files_parts << dimmed("#{results.skipped.count} skipped") if results.skipped.any?
580
+
581
+ puts " #{label("Files")} #{files_parts.join(" | ")}"
573
582
  end
574
583
 
575
584
  parser_parts = []
@@ -581,8 +590,9 @@ module Herb
581
590
  parser_parts << stat(results.analyze_parse_error.count, "analyze", :yellow) if results.analyze_parse_error.any?
582
591
  puts " #{label("Parser")} #{parser_parts.join(" | ")}"
583
592
 
584
- skipped = total - passed - results.validation_error.count - results.compilation_failed.count -
585
- results.strict_compilation_failed.count - results.invalid_ruby.count
593
+ not_compiled = total - passed - results.skipped.count - results.validation_error.count -
594
+ results.compilation_failed.count - results.strict_compilation_failed.count -
595
+ results.invalid_ruby.count
586
596
 
587
597
  engine_parts = []
588
598
  engine_parts << stat(passed, "compiled", :green)
@@ -590,13 +600,17 @@ module Herb
590
600
  engine_parts << stat(results.compilation_failed.count, "compilation", :red) if results.compilation_failed.any?
591
601
  engine_parts << stat(results.strict_compilation_failed.count, "strict", :yellow) if results.strict_compilation_failed.any?
592
602
  engine_parts << stat(results.invalid_ruby.count, "produced invalid Ruby", :red) if results.invalid_ruby.any?
593
- engine_parts << dimmed("#{skipped} skipped") if skipped.positive?
603
+ engine_parts << dimmed("#{not_compiled} not compiled") if not_compiled.positive?
594
604
  puts " #{label("Engine")} #{engine_parts.join(" | ")}"
595
605
 
596
606
  if results.timeout.any?
597
607
  puts " #{label("Timeout")} #{stat(results.timeout.count, "timed out", :yellow)}"
598
608
  end
599
609
 
610
+ if results.skipped.any?
611
+ puts " #{label("Skipped")} #{dimmed("#{results.skipped.count} #{pluralize(results.skipped.count, "file")}")}"
612
+ end
613
+
600
614
  if duration
601
615
  puts " #{label("Duration")} #{cyan(format_duration(duration))}"
602
616
  end
@@ -630,6 +644,7 @@ module Herb
630
644
  log.puts ""
631
645
  log.puts "--- Other ---"
632
646
  log.puts "⏱️ Timed out: #{results.timeout.count} (#{percentage(results.timeout.count, total)}%)"
647
+ log.puts "⊘ Skipped: #{results.skipped.count} (#{percentage(results.skipped.count, total)}%)"
633
648
 
634
649
  return unless duration
635
650
 
@@ -639,10 +654,27 @@ module Herb
639
654
  def print_file_lists(results, log)
640
655
  log_file_lists(results, log)
641
656
 
642
- return unless results.problem_files.any?
643
-
644
657
  printed_section = false
645
658
 
659
+ if results.skipped.any?
660
+ printed_section = true
661
+
662
+ puts "\n"
663
+ puts " #{bold("Skipped files:")}"
664
+ puts " #{dimmed("These files were parsed successfully but skipped for compilation by the engine.")}"
665
+
666
+ results.skipped.each do |file|
667
+ relative = relative_path(file)
668
+ reason = results.skip_reasons[file]
669
+
670
+ puts ""
671
+ puts " #{cyan(relative)}:"
672
+ puts " #{dimmed("⊘")} #{dimmed(reason)}"
673
+ end
674
+ end
675
+
676
+ return unless results.problem_files.any?
677
+
646
678
  ISSUE_TYPES.each do |type|
647
679
  file_list = results.send(type[:key])
648
680
  next unless file_list.any?
@@ -682,6 +714,15 @@ module Herb
682
714
  end
683
715
 
684
716
  def log_file_lists(results, log)
717
+ if results.skipped.any?
718
+ log.puts "\n#{heading("Files: Skipped")}"
719
+
720
+ results.skipped.each do |file|
721
+ reason = results.skip_reasons[file]
722
+ log.puts "#{file} - #{reason}"
723
+ end
724
+ end
725
+
685
726
  ISSUE_TYPES.each do |type|
686
727
  file_list = results.send(type[:key])
687
728
  next unless file_list.any?
data/lib/herb/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Herb
5
- VERSION = "0.9.3"
5
+ VERSION = "0.9.4"
6
6
  end
@@ -5,6 +5,16 @@ module Herb
5
5
  class Compiler < ::Herb::Visitor
6
6
  EXPRESSION_TOKEN_TYPES: untyped
7
7
 
8
+ TRAILING_WHITESPACE: ::Regexp
9
+
10
+ TRAILING_INDENTATION: ::Regexp
11
+
12
+ TRAILING_INDENTATION_CAPTURE: ::Regexp
13
+
14
+ WHITESPACE_ONLY: ::Regexp
15
+
16
+ WHITESPACE_ONLY_CAPTURE: ::Regexp
17
+
8
18
  attr_reader tokens: untyped
9
19
 
10
20
  def initialize: (untyped engine, ?untyped options) -> untyped
@@ -85,6 +95,8 @@ module Herb
85
95
 
86
96
  private
87
97
 
98
+ def check_for_escaped_erb_tag!: (untyped opening) -> untyped
99
+
88
100
  def current_context: () -> untyped
89
101
 
90
102
  def push_context: (untyped context) -> untyped
@@ -134,6 +146,10 @@ module Herb
134
146
 
135
147
  def preceding_token_ends_with_newline?: () -> untyped
136
148
 
149
+ def left_trim?: (untyped node) -> untyped
150
+
151
+ def right_trim?: (untyped node) -> untyped
152
+
137
153
  def last_text_token: () -> untyped
138
154
 
139
155
  def extract_leading_space: () -> untyped
data/sig/herb/engine.rbs CHANGED
@@ -27,6 +27,9 @@ module Herb
27
27
  class CompilationError < StandardError
28
28
  end
29
29
 
30
+ class GeneratorTemplateError < CompilationError
31
+ end
32
+
30
33
  class InvalidRubyError < CompilationError
31
34
  attr_reader compiled_source: untyped
32
35
 
@@ -75,8 +75,7 @@ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
75
75
 
76
76
  hb_string_T opening = erb_content_node->tag_opening->value;
77
77
 
78
- if (!hb_string_equals(opening, hb_string("<%%")) && !hb_string_equals(opening, hb_string("<%%="))
79
- && !hb_string_equals(opening, hb_string("<%#")) && !hb_string_equals(opening, hb_string("<%graphql"))) {
78
+ if (!hb_string_equals(opening, hb_string("<%#")) && !hb_string_equals(opening, hb_string("<%graphql"))) {
80
79
  analyzed_ruby_T* analyzed = herb_analyze_ruby(erb_content_node->content->value);
81
80
 
82
81
  erb_content_node->parsed = true;
@@ -1032,7 +1031,7 @@ void herb_analyze_parse_tree(
1032
1031
 
1033
1032
  herb_visit_node((AST_NODE_T*) document, detect_invalid_erb_structures, &invalid_context);
1034
1033
 
1035
- herb_analyze_parse_errors(document, source, allocator);
1034
+ herb_analyze_parse_errors(document, source, options, allocator);
1036
1035
 
1037
1036
  herb_parser_match_html_tags_post_analyze(document, options, allocator);
1038
1037
 
@@ -11,6 +11,37 @@
11
11
  #include <stdlib.h>
12
12
  #include <string.h>
13
13
 
14
+ static bool document_has_anonymous_keyword_rest(AST_DOCUMENT_NODE_T* document) {
15
+ if (!document || !document->children) { return false; }
16
+
17
+ for (size_t index = 0; index < hb_array_size(document->children); index++) {
18
+ AST_NODE_T* child = hb_array_get(document->children, index);
19
+ if (!child || child->type != AST_ERB_STRICT_LOCALS_NODE) { continue; }
20
+
21
+ AST_ERB_STRICT_LOCALS_NODE_T* strict_locals_node = (AST_ERB_STRICT_LOCALS_NODE_T*) child;
22
+ if (!strict_locals_node->locals) { continue; }
23
+
24
+ for (size_t local_index = 0; local_index < hb_array_size(strict_locals_node->locals); local_index++) {
25
+ AST_RUBY_STRICT_LOCAL_NODE_T* local = hb_array_get(strict_locals_node->locals, local_index);
26
+ if (local && local->double_splat && local->name == NULL) { return true; }
27
+ }
28
+ }
29
+
30
+ return false;
31
+ }
32
+
33
+ static bool should_skip_forwarding_error(
34
+ const pm_diagnostic_t* error,
35
+ bool strict_locals_enabled,
36
+ bool has_anonymous_keyword_rest
37
+ ) {
38
+ if (error->diag_id != PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR) { return false; }
39
+
40
+ if (!strict_locals_enabled) { return true; }
41
+
42
+ return has_anonymous_keyword_rest;
43
+ }
44
+
14
45
  static void parse_erb_content_errors(AST_NODE_T* erb_node, const char* source, hb_allocator_T* allocator) {
15
46
  if (!erb_node || erb_node->type != AST_ERB_CONTENT_NODE) { return; }
16
47
  AST_ERB_CONTENT_NODE_T* content_node = (AST_ERB_CONTENT_NODE_T*) erb_node;
@@ -45,11 +76,19 @@ static void parse_erb_content_errors(AST_NODE_T* erb_node, const char* source, h
45
76
  free(content);
46
77
  }
47
78
 
48
- void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source, hb_allocator_T* allocator) {
79
+ void herb_analyze_parse_errors(
80
+ AST_DOCUMENT_NODE_T* document,
81
+ const char* source,
82
+ const parser_options_T* parser_options,
83
+ hb_allocator_T* allocator
84
+ ) {
49
85
  char* extracted_ruby = herb_extract_ruby_with_semicolons(source, allocator);
50
86
 
51
87
  if (!extracted_ruby) { return; }
52
88
 
89
+ bool strict_locals_enabled = parser_options && parser_options->strict_locals;
90
+ bool has_anonymous_keyword_rest = strict_locals_enabled && document_has_anonymous_keyword_rest(document);
91
+
53
92
  pm_parser_t parser;
54
93
  pm_options_t options = { 0, .partial_script = true };
55
94
  pm_parser_init(&parser, (const uint8_t*) extracted_ruby, strlen(extracted_ruby), &options);
@@ -58,6 +97,8 @@ void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source
58
97
 
59
98
  for (const pm_diagnostic_t* error = (const pm_diagnostic_t*) parser.error_list.head; error != NULL;
60
99
  error = (const pm_diagnostic_t*) error->node.next) {
100
+ if (should_skip_forwarding_error(error, strict_locals_enabled, has_anonymous_keyword_rest)) { continue; }
101
+
61
102
  size_t error_offset = (size_t) (error->location.start - parser.start);
62
103
 
63
104
  if (strstr(error->message, "unexpected ';'") != NULL) {
@@ -440,6 +440,20 @@ static hb_array_T* extract_strict_locals(
440
440
 
441
441
  hb_array_append(locals, local);
442
442
  hb_allocator_dealloc(allocator, name);
443
+ } else {
444
+ size_t params_in_content = (size_t) (params_open - content_bytes);
445
+ size_t operator_prism_start = (size_t) (keyword_rest_param->operator_loc.start - synthetic_start);
446
+ size_t operator_prism_end = (size_t) (keyword_rest_param->operator_loc.end - synthetic_start);
447
+ size_t content_start = params_in_content + (operator_prism_start - strlen(SYNTHETIC_PREFIX));
448
+ size_t content_end = params_in_content + (operator_prism_end - strlen(SYNTHETIC_PREFIX));
449
+
450
+ position_T start = byte_offset_to_position(source, erb_content_byte_offset + content_start);
451
+ position_T end = byte_offset_to_position(source, erb_content_byte_offset + content_end);
452
+
453
+ AST_RUBY_STRICT_LOCAL_NODE_T* local =
454
+ ast_ruby_strict_local_node_init(NULL, NULL, false, true, start, end, hb_array_init(0, allocator), allocator);
455
+
456
+ hb_array_append(locals, local);
443
457
  }
444
458
  }
445
459
 
@@ -44,7 +44,12 @@ typedef struct {
44
44
  hb_allocator_T* allocator;
45
45
  } invalid_erb_context_T;
46
46
 
47
- void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source, hb_allocator_T* allocator);
47
+ void herb_analyze_parse_errors(
48
+ AST_DOCUMENT_NODE_T* document,
49
+ const char* source,
50
+ const parser_options_T* options,
51
+ hb_allocator_T* allocator
52
+ );
48
53
  void herb_analyze_parse_tree(
49
54
  AST_DOCUMENT_NODE_T* document,
50
55
  const char* source,
@@ -1,6 +1,6 @@
1
1
  #ifndef HERB_VERSION_H
2
2
  #define HERB_VERSION_H
3
3
 
4
- #define HERB_VERSION "0.9.3"
4
+ #define HERB_VERSION "0.9.4"
5
5
 
6
6
  #endif
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.3
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth