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 +4 -4
- data/lib/herb/engine/compiler.rb +48 -20
- data/lib/herb/engine.rb +3 -0
- data/lib/herb/project.rb +58 -17
- data/lib/herb/version.rb +1 -1
- data/sig/herb/engine/compiler.rbs +16 -0
- data/sig/herb/engine.rbs +3 -0
- data/src/analyze/analyze.c +2 -3
- data/src/analyze/parse_errors.c +42 -1
- data/src/analyze/strict_locals.c +14 -0
- data/src/include/analyze/analyze.h +6 -1
- data/src/include/version.h +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dad9b36758689e8b12d360b0ab22322e820762b4b065556d90a5d1d2cdd319b0
|
|
4
|
+
data.tar.gz: d298f4e91debfefac4725f7647f671d4eeb571a0d6e52d19b2452eeaf5c4b497
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a935b405301ba5ffc3531ae41157321278d4f6f56a56a15d7e287605493eaf2d4fe9a4a43e753001ea094d462d7972c50691c569ea01c7e2470e8f41cc8d4623
|
|
7
|
+
data.tar.gz: 1157b317060949235da509fb8f7b33f424ade19a1288b3319140c233e6df53afc560042ca4713ee71abfdb00975b673844d0bd16d01259ea2b0fbe91cce43ae8
|
data/lib/herb/engine/compiler.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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 =~
|
|
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 =~
|
|
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]
|
|
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 =~
|
|
574
|
-
text.sub!(
|
|
575
|
-
elsif text =~
|
|
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 =
|
|
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[
|
|
648
|
+
removed = text[TRAILING_WHITESPACE] || ""
|
|
621
649
|
|
|
622
|
-
if text =~
|
|
623
|
-
text.sub!(
|
|
650
|
+
if text =~ TRAILING_INDENTATION
|
|
651
|
+
text.sub!(TRAILING_WHITESPACE, "")
|
|
624
652
|
token[1] = text
|
|
625
|
-
elsif text =~
|
|
653
|
+
elsif text =~ WHITESPACE_ONLY
|
|
626
654
|
text.replace("")
|
|
627
655
|
token[1] = text
|
|
628
656
|
end
|
data/lib/herb/engine.rb
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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("#{
|
|
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
|
@@ -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
data/src/analyze/analyze.c
CHANGED
|
@@ -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("
|
|
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
|
|
data/src/analyze/parse_errors.c
CHANGED
|
@@ -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(
|
|
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) {
|
data/src/analyze/strict_locals.c
CHANGED
|
@@ -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(
|
|
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,
|
data/src/include/version.h
CHANGED