herb 0.10.1-arm-linux-gnu → 0.10.3-arm-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c50af1ad11b3ef398a89914a8750846b1ca78043493cf043e74d9103a538d36d
4
- data.tar.gz: 0f990d09eafb6f3b2b33146b6c70c371a430bfbd195d5503773cb23c11500c52
3
+ metadata.gz: 0216afda4c85b7e2509251face5bc516c057ba8bc9282666bec6aa8eddecd4fd
4
+ data.tar.gz: 0fd52650bda4254b48c5e8e50c3e4033448520eb05f4c8a1f7b96ccde3638342
5
5
  SHA512:
6
- metadata.gz: d376f262651fdd09f9979d8a449f85f019fb2a48d7631fe197952439a5662db8dd8030bfdeb9049e73acd7de9e5acd534c74d29c0e4b1ad4e5d551c5ef96321e
7
- data.tar.gz: c3aca47f9b6575930a31888de3166b79fc7d97335b8e801fba94d4855665ac59bae392e5866e7efe65a601a427c6bec348f25f72ea1a6b8d5ba1dde8dac7c840
6
+ metadata.gz: 88e3352adad7331dea737ed9cff30b439d7a4fd4245d2437ba652d7b85328d5fa7ef2381609b70f685c72684c2f98129ee776ac42dd190c4e5839394dd7d9210
7
+ data.tar.gz: 97ba9f70499b6ae5d6f5b0b59a1f085240a75a6d83709b19ec008ef79de8759105f20bd15af6c4a8be3a392ce5889d86ca18eddebc82f3a9b5a65a1e8da133a6
data/Makefile CHANGED
@@ -53,7 +53,7 @@ production_flags = $(warning_flags) -O3 -march=native -flto
53
53
  shared_library_flags = -fPIC
54
54
 
55
55
  # Default build mode (change this as needed)
56
- flags = $(warning_flags) $(debug_flags) $(prism_flags) -std=c99
56
+ flags = $(warning_flags) $(debug_flags) $(prism_flags) -std=gnu99
57
57
 
58
58
  # Separate test compilation flags
59
59
  test_flags = $(debug_flags) $(prism_flags) -std=gnu99
data/config.yml CHANGED
@@ -486,6 +486,16 @@ errors:
486
486
  - name: segment
487
487
  type: token
488
488
 
489
+ - name: TimeoutError
490
+ message:
491
+ template: "Parsing timed out after %zu ms."
492
+ arguments:
493
+ - timeout_ms
494
+
495
+ fields:
496
+ - name: timeout_ms
497
+ type: size_t
498
+
489
499
  warnings:
490
500
  fields: []
491
501
  types: []
@@ -53,6 +53,7 @@ static VALUE cStrictLocalsMissingParenthesisError;
53
53
  static VALUE cStrictLocalsDuplicateDeclarationError;
54
54
  static VALUE cVoidElementContentError;
55
55
  static VALUE cDotNotationCasingError;
56
+ static VALUE cTimeoutError;
56
57
 
57
58
  void rb_init_error_classes(void) {
58
59
  mErrors = rb_define_module_under(mHerb, "Errors");
@@ -94,6 +95,7 @@ void rb_init_error_classes(void) {
94
95
  cStrictLocalsDuplicateDeclarationError = rb_define_class_under(mErrors, "StrictLocalsDuplicateDeclarationError", cError);
95
96
  cVoidElementContentError = rb_define_class_under(mErrors, "VoidElementContentError", cError);
96
97
  cDotNotationCasingError = rb_define_class_under(mErrors, "DotNotationCasingError", cError);
98
+ cTimeoutError = rb_define_class_under(mErrors, "TimeoutError", cError);
97
99
  }
98
100
 
99
101
  static VALUE rb_unexpected_error_from_c_struct(UNEXPECTED_ERROR_T* unexpected_error) {
@@ -906,6 +908,27 @@ static VALUE rb_dot_notation_casing_error_from_c_struct(DOT_NOTATION_CASING_ERRO
906
908
  return rb_class_new_instance(4, args, cDotNotationCasingError);
907
909
  };
908
910
 
911
+ static VALUE rb_timeout_error_from_c_struct(TIMEOUT_ERROR_T* timeout_error) {
912
+ if (timeout_error == NULL) { return Qnil; }
913
+
914
+ ERROR_T* error = &timeout_error->base;
915
+
916
+ VALUE type = rb_string_from_hb_string(error_type_to_string(error));
917
+ VALUE location = rb_location_from_c_struct(error->location);
918
+ VALUE message = rb_string_from_hb_string(error->message);
919
+
920
+ VALUE timeout_error_timeout_ms = ULONG2NUM(timeout_error->timeout_ms);
921
+
922
+ VALUE args[4] = {
923
+ type,
924
+ location,
925
+ message,
926
+ timeout_error_timeout_ms
927
+ };
928
+
929
+ return rb_class_new_instance(4, args, cTimeoutError);
930
+ };
931
+
909
932
 
910
933
  VALUE rb_error_from_c_struct(ERROR_T* error) {
911
934
  if (!error) { return Qnil; }
@@ -948,6 +971,7 @@ VALUE rb_error_from_c_struct(ERROR_T* error) {
948
971
  case STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR: return rb_strict_locals_duplicate_declaration_error_from_c_struct((STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR_T*) error); break;
949
972
  case VOID_ELEMENT_CONTENT_ERROR: return rb_void_element_content_error_from_c_struct((VOID_ELEMENT_CONTENT_ERROR_T*) error); break;
950
973
  case DOT_NOTATION_CASING_ERROR: return rb_dot_notation_casing_error_from_c_struct((DOT_NOTATION_CASING_ERROR_T*) error); break;
974
+ case TIMEOUT_ERROR: return rb_timeout_error_from_c_struct((TIMEOUT_ERROR_T*) error); break;
951
975
  }
952
976
 
953
977
  return Qnil;
data/ext/herb/extension.c CHANGED
@@ -174,6 +174,21 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) {
174
174
  if (NIL_P(html)) { html = rb_hash_lookup(options, ID2SYM(rb_intern("html"))); }
175
175
  if (!NIL_P(html) && !RTEST(html)) { parser_options.html = false; }
176
176
 
177
+ VALUE timeout = rb_hash_lookup(options, rb_utf8_str_new_cstr("timeout"));
178
+ if (NIL_P(timeout)) { timeout = rb_hash_lookup(options, ID2SYM(rb_intern("timeout"))); }
179
+ if (!NIL_P(timeout)) { parser_options.timeout_ms = (uint32_t) (NUM2DBL(timeout) * 1000); }
180
+
181
+ VALUE max_errors_sentinel = ID2SYM(rb_intern("__not_set__"));
182
+ VALUE max_errors = rb_hash_lookup2(options, rb_utf8_str_new_cstr("max_errors"), max_errors_sentinel);
183
+
184
+ if (max_errors == max_errors_sentinel) {
185
+ max_errors = rb_hash_lookup2(options, ID2SYM(rb_intern("max_errors")), max_errors_sentinel);
186
+ }
187
+
188
+ if (max_errors != max_errors_sentinel) {
189
+ parser_options.max_errors = NIL_P(max_errors) ? 0 : (uint32_t) NUM2UINT(max_errors);
190
+ }
191
+
177
192
  VALUE arena_stats = rb_hash_lookup(options, rb_utf8_str_new_cstr("arena_stats"));
178
193
  if (NIL_P(arena_stats)) { arena_stats = rb_hash_lookup(options, ID2SYM(rb_intern("arena_stats"))); }
179
194
  if (!NIL_P(arena_stats) && RTEST(arena_stats)) { print_arena_stats = true; }
@@ -95,6 +95,13 @@ VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source, const parser_
95
95
  rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes")), options->prism_nodes ? Qtrue : Qfalse);
96
96
  rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes_deep")), options->prism_nodes_deep ? Qtrue : Qfalse);
97
97
  rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_program")), options->prism_program ? Qtrue : Qfalse);
98
+ rb_hash_aset(kwargs, ID2SYM(rb_intern("timeout")), DBL2NUM((double) options->timeout_ms / 1000.0));
99
+
100
+ rb_hash_aset(
101
+ kwargs,
102
+ ID2SYM(rb_intern("max_errors")),
103
+ options->max_errors == 0 ? Qnil : UINT2NUM(options->max_errors)
104
+ );
98
105
 
99
106
  VALUE parser_options_args[1] = { kwargs };
100
107
  VALUE parser_options = rb_class_new_instance_kw(1, parser_options_args, cParserOptions, RB_PASS_KEYWORDS);
data/herb.gemspec CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  "lib/**/*.rb",
32
32
  "lib/**/*.yml",
33
33
  "sig/**/*.rbs",
34
+ "sig/manifest.yaml",
34
35
  "src/**/*.{c,h}",
35
36
  "ext/**/*.{c,h}",
36
37
  "templates/**/*.{rb,erb}",
data/lib/herb/3.2/herb.so CHANGED
Binary file
data/lib/herb/3.3/herb.so CHANGED
Binary file
data/lib/herb/3.4/herb.so CHANGED
Binary file
data/lib/herb/4.0/herb.so CHANGED
Binary file
@@ -784,7 +784,7 @@ module Herb
784
784
  def ensure_parallel!
785
785
  return if defined?(Parallel)
786
786
 
787
- Herb.ensure_installed { gem "parallel" } # steep:ignore
787
+ Herb.ensure_installed { gem "parallel" }
788
788
  end
789
789
 
790
790
  def collect_ruby_render_references
data/lib/herb/cli.rb CHANGED
@@ -8,7 +8,7 @@ require "optparse"
8
8
  class Herb::CLI
9
9
  include Herb::Colors
10
10
 
11
- attr_accessor :json, :silent, :log_file, :no_timing, :local, :escape, :no_escape, :freeze, :debug, :tool, :strict, :analyze, :track_whitespace, :verbose, :isolate, :arena_stats, :leak_check, :action_view_helpers, :trim, :optimize
11
+ attr_accessor :json, :silent, :log_file, :no_timing, :local, :escape, :no_escape, :freeze, :debug, :tool, :strict, :analyze, :track_whitespace, :verbose, :isolate, :arena_stats, :leak_check, :action_view_helpers, :trim, :optimize, :file_timeout
12
12
 
13
13
  def initialize(args)
14
14
  @args = args
@@ -154,6 +154,7 @@ class Herb::CLI
154
154
  project.silent = silent
155
155
  project.verbose = verbose || ci?
156
156
  project.isolate = isolate
157
+ project.file_timeout = file_timeout if file_timeout
157
158
  project.validate_ruby = true
158
159
  project.arena_stats = arena_stats
159
160
  project.leak_check = leak_check
@@ -269,6 +270,10 @@ class Herb::CLI
269
270
  self.isolate = true
270
271
  end
271
272
 
273
+ parser.on("--timeout SECONDS", Float, "Per-file timeout for parse + compile (for analyze command) (default: #{Herb::Project::DEFAULT_FILE_TIMEOUT})") do |seconds|
274
+ self.file_timeout = seconds
275
+ end
276
+
272
277
  parser.on("--log-file", "Enable log file generation") do
273
278
  self.log_file = true
274
279
  end
@@ -336,7 +336,7 @@ module Herb
336
336
  end
337
337
 
338
338
  def in_excluded_context?
339
- excluded_tags = ["script", "style", "head", "textarea", "pre", "svg", "math"]
339
+ excluded_tags = ["script", "style", "head", "title", "textarea", "pre", "svg", "math"]
340
340
  return true if excluded_tags.any? { |tag| @element_stack.include?(tag) }
341
341
 
342
342
  if @erb_block_stack.any? { |node| javascript_tag?(node.content.value.strip) || include_debug_disable_comment?(node.content.value.strip) }
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
4
+
3
5
  module Herb
4
6
  class Engine
5
7
  class ParserErrorOverlay
data/lib/herb/errors.rb CHANGED
@@ -1526,5 +1526,44 @@ module Herb
1526
1526
  end
1527
1527
  end
1528
1528
 
1529
+ class TimeoutError < Error
1530
+ include Colors
1531
+
1532
+ #| timeout_ms: Integer?,
1533
+ #| }
1534
+
1535
+ attr_reader :timeout_ms #: Integer?
1536
+
1537
+ #: (String, Location?, String, Integer) -> void
1538
+ def initialize(type, location, message, timeout_ms)
1539
+ super(type, location, message)
1540
+ @timeout_ms = timeout_ms
1541
+ end
1542
+
1543
+ #: () -> String
1544
+ def inspect
1545
+ tree_inspect.rstrip.gsub(/\s+$/, "")
1546
+ end
1547
+
1548
+ #: () -> serialized_timeout_error
1549
+ def to_hash
1550
+ super.merge(
1551
+ timeout_ms: timeout_ms
1552
+ ) #: Herb::serialized_timeout_error
1553
+ end
1554
+
1555
+ #: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
1556
+ def tree_inspect(indent: 0, depth: 0, depth_limit: 25)
1557
+ output = +""
1558
+
1559
+ output += white("@ #{bold(red(error_name))} #{dimmed("(location: #{location&.tree_inspect})\n")}")
1560
+ output += white("├── message: #{green(message.inspect)}\n")
1561
+ output += white("└── timeout_ms: #{green(timeout_ms.inspect)}\n")
1562
+ output += %(\n)
1563
+
1564
+ output.gsub(/^/, " " * indent)
1565
+ end
1566
+ end
1567
+
1529
1568
  end
1530
1569
  end
@@ -12,6 +12,8 @@ module Herb
12
12
  attr_reader :prism_program #: bool
13
13
  attr_reader :prism_nodes #: bool
14
14
  attr_reader :prism_nodes_deep #: bool
15
+ attr_reader :timeout #: Numeric
16
+ attr_reader :max_errors #: Integer?
15
17
 
16
18
  DEFAULT_STRICT = true #: bool
17
19
  DEFAULT_TRACK_WHITESPACE = false #: bool
@@ -23,9 +25,11 @@ module Herb
23
25
  DEFAULT_PRISM_PROGRAM = false #: bool
24
26
  DEFAULT_PRISM_NODES = false #: bool
25
27
  DEFAULT_PRISM_NODES_DEEP = false #: bool
28
+ DEFAULT_TIMEOUT = 1 #: Numeric
29
+ DEFAULT_MAX_ERRORS = 25 #: Integer
26
30
 
27
- #: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool) -> void
28
- def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM)
31
+ #: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void
32
+ def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, timeout: DEFAULT_TIMEOUT, max_errors: DEFAULT_MAX_ERRORS)
29
33
  @strict = strict
30
34
  @track_whitespace = track_whitespace
31
35
  @analyze = analyze
@@ -36,9 +40,11 @@ module Herb
36
40
  @prism_nodes = prism_nodes
37
41
  @prism_nodes_deep = prism_nodes_deep
38
42
  @prism_program = prism_program
43
+ @timeout = timeout
44
+ @max_errors = max_errors
39
45
  end
40
46
 
41
- #: () -> Hash[Symbol, bool]
47
+ #: () -> Hash[Symbol, (bool | Numeric | nil)]
42
48
  def to_h
43
49
  {
44
50
  strict: @strict,
@@ -51,6 +57,8 @@ module Herb
51
57
  prism_nodes: @prism_nodes,
52
58
  prism_nodes_deep: @prism_nodes_deep,
53
59
  prism_program: @prism_program,
60
+ timeout: @timeout,
61
+ max_errors: @max_errors,
54
62
  }
55
63
  end
56
64
 
@@ -66,7 +74,9 @@ module Herb
66
74
  "strict_locals=#{@strict_locals}\n " \
67
75
  "prism_nodes=#{@prism_nodes}\n " \
68
76
  "prism_nodes_deep=#{@prism_nodes_deep}\n " \
69
- "prism_program=#{@prism_program}>"
77
+ "prism_program=#{@prism_program}\n " \
78
+ "timeout=#{@timeout}\n " \
79
+ "max_errors=#{@max_errors}>"
70
80
  end
71
81
  end
72
82
  end
data/lib/herb/project.rb CHANGED
@@ -12,7 +12,9 @@ module Herb
12
12
  class Project
13
13
  include Colors
14
14
 
15
- attr_accessor :project_path, :output_file, :no_log_file, :no_timing, :silent, :verbose, :isolate, :validate_ruby, :file_paths, :arena_stats, :leak_check
15
+ attr_accessor :project_path, :output_file, :no_log_file, :no_timing, :silent, :verbose, :isolate, :validate_ruby, :file_paths, :arena_stats, :leak_check, :file_timeout
16
+
17
+ DEFAULT_FILE_TIMEOUT = 1 # seconds per file for parse + compile
16
18
 
17
19
  # Known error types that indicate issues in the user's template, not bugs in the parser.
18
20
  TEMPLATE_ERRORS = [
@@ -126,6 +128,7 @@ module Herb
126
128
 
127
129
  date = Time.now.strftime("%Y-%m-%d_%H-%M-%S")
128
130
  @output_file = output_file || "#{date}_erb_parsing_result_#{@project_path.basename}.log"
131
+ @file_timeout = DEFAULT_FILE_TIMEOUT
129
132
  end
130
133
 
131
134
  def configuration
@@ -354,7 +357,7 @@ module Herb
354
357
  result[:leak_check] = capture_leak_check(file_content)
355
358
  end
356
359
 
357
- Timeout.timeout(1) do
360
+ Timeout.timeout(file_timeout) do
358
361
  parse_result = Herb.parse(file_content)
359
362
 
360
363
  if parse_result.failed?
@@ -369,7 +372,7 @@ module Herb
369
372
  result
370
373
  rescue Timeout::Error
371
374
  result.merge(status: :timeout, file_content: file_content,
372
- log: "⏱️ Parsing #{file_path} timed out after 1 second")
375
+ log: "⏱️ Parsing #{file_path} timed out after #{file_timeout} #{pluralize(file_timeout, "second")}")
373
376
  rescue StandardError => e
374
377
  file_content ||= begin
375
378
  File.read(file_path)
@@ -388,7 +391,7 @@ module Herb
388
391
  stdout_file = Tempfile.new("stdout")
389
392
  stderr_file = Tempfile.new("stderr")
390
393
 
391
- Timeout.timeout(1) do
394
+ Timeout.timeout(file_timeout) do
392
395
  pid = Process.fork do
393
396
  $stdout.reopen(stdout_file.path, "w")
394
397
  $stderr.reopen(stderr_file.path, "w")
@@ -431,7 +434,7 @@ module Herb
431
434
  nil
432
435
  end
433
436
 
434
- { file_path: file_path, status: :timeout, file_content: file_content, 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 #{file_timeout} #{pluralize(file_timeout, "second")}" }
435
438
  rescue StandardError => e
436
439
  file_content ||= begin
437
440
  File.read(file_path)
data/lib/herb/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Herb
5
- VERSION = "0.10.1"
5
+ VERSION = "0.10.3"
6
6
  end
data/sig/herb/errors.rbs CHANGED
@@ -694,5 +694,23 @@ module Herb
694
694
  # : (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
695
695
  def tree_inspect: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
696
696
  end
697
+
698
+ class TimeoutError < Error
699
+ include Colors
700
+
701
+ attr_reader timeout_ms: Integer?
702
+
703
+ # : (String, Location?, String, Integer) -> void
704
+ def initialize: (String, Location?, String, Integer) -> void
705
+
706
+ # : () -> String
707
+ def inspect: () -> String
708
+
709
+ # : () -> serialized_timeout_error
710
+ def to_hash: () -> serialized_timeout_error
711
+
712
+ # : (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
713
+ def tree_inspect: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
714
+ end
697
715
  end
698
716
  end
@@ -22,6 +22,10 @@ module Herb
22
22
 
23
23
  attr_reader prism_nodes_deep: bool
24
24
 
25
+ attr_reader timeout: Numeric
26
+
27
+ attr_reader max_errors: Integer?
28
+
25
29
  DEFAULT_STRICT: bool
26
30
 
27
31
  DEFAULT_TRACK_WHITESPACE: bool
@@ -42,11 +46,15 @@ module Herb
42
46
 
43
47
  DEFAULT_PRISM_NODES_DEEP: bool
44
48
 
45
- # : (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool) -> void
46
- def initialize: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool) -> void
49
+ DEFAULT_TIMEOUT: Numeric
50
+
51
+ DEFAULT_MAX_ERRORS: Integer
52
+
53
+ # : (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void
54
+ def initialize: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void
47
55
 
48
- # : () -> Hash[Symbol, bool]
49
- def to_h: () -> Hash[Symbol, bool]
56
+ # : () -> Hash[Symbol, (bool | Numeric | nil)]
57
+ def to_h: () -> Hash[Symbol, bool | Numeric | nil]
50
58
 
51
59
  # : () -> String
52
60
  def inspect: () -> String
data/sig/manifest.yaml ADDED
@@ -0,0 +1,13 @@
1
+ dependencies:
2
+ - name: delegate
3
+ - name: did_you_mean
4
+ - name: fileutils
5
+ - name: json
6
+ - name: optparse
7
+ - name: pathname
8
+ - name: socket
9
+ - name: stringio
10
+ - name: tempfile
11
+ - name: time
12
+ - name: timeout
13
+ - name: yaml
@@ -169,4 +169,8 @@ module Herb
169
169
  segment: Herb::Token,
170
170
  }
171
171
 
172
+ type serialized_timeout_error = serialized_error & {
173
+ timeout_ms: Integer,
174
+ }
175
+
172
176
  end
@@ -5,6 +5,7 @@
5
5
  #include "../../include/analyze/analyze.h"
6
6
  #include "../../include/ast/ast_nodes.h"
7
7
  #include "../../include/herb.h"
8
+ #include "../../include/lexer/token.h"
8
9
  #include "../../include/lib/hb_allocator.h"
9
10
  #include "../../include/lib/hb_array.h"
10
11
  #include "../../include/lib/hb_buffer.h"
@@ -1426,6 +1427,8 @@ void transform_tag_helper_array(hb_array_T* array, analyze_ruby_context_T* conte
1426
1427
  if (child->type == AST_ERB_BLOCK_NODE) {
1427
1428
  AST_ERB_BLOCK_NODE_T* block_node = (AST_ERB_BLOCK_NODE_T*) child;
1428
1429
 
1430
+ if (token_is_escaped_erb_tag_opening(block_node->tag_opening)) { continue; }
1431
+
1429
1432
  if (block_node->tag_opening && !hb_string_is_empty(block_node->tag_opening->value)) {
1430
1433
  const char* opening_string = block_node->tag_opening->value.data;
1431
1434
  if (opening_string && strstr(opening_string, "=") == NULL) { continue; }
@@ -1455,6 +1458,8 @@ void transform_tag_helper_array(hb_array_T* array, analyze_ruby_context_T* conte
1455
1458
  AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) child;
1456
1459
  token_T* tag_opening = erb_node->tag_opening;
1457
1460
 
1461
+ if (token_is_escaped_erb_tag_opening(tag_opening)) { continue; }
1462
+
1458
1463
  if (tag_opening && !hb_string_is_empty(tag_opening->value)) {
1459
1464
  const char* opening_string = tag_opening->value.data;
1460
1465
 
@@ -277,7 +277,13 @@ bool search_in_nodes(const pm_node_t* node, void* data) {
277
277
  analyzed_ruby_T* analyzed = (analyzed_ruby_T*) data;
278
278
 
279
279
  if (node->type == PM_IN_NODE) { analyzed->in_node_count++; }
280
- if (node->type == PM_MATCH_PREDICATE_NODE) { analyzed->in_node_count++; }
280
+ if (node->type == PM_CASE_MATCH_NODE) {
281
+ const pm_case_match_node_t* case_match_node = (const pm_case_match_node_t*) node;
282
+
283
+ if (case_match_node->predicate != NULL && case_match_node->predicate->type == PM_MATCH_PREDICATE_NODE) {
284
+ analyzed->in_node_count++;
285
+ }
286
+ }
281
287
 
282
288
  pm_visit_child_nodes(node, search_in_nodes, analyzed);
283
289
 
@@ -110,11 +110,10 @@ static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* da
110
110
  if (call->block != NULL && call->block->type == PM_BLOCK_NODE) {
111
111
  pm_block_node_t* block_node = (pm_block_node_t*) call->block;
112
112
 
113
- bool has_do_opening = is_do_block(block_node->opening_loc);
114
- bool has_brace_opening = is_brace_block(block_node->opening_loc);
115
- bool has_valid_brace_closing = is_closing_brace(block_node->closing_loc);
113
+ bool has_opening = is_do_block(block_node->opening_loc) || is_brace_block(block_node->opening_loc);
114
+ bool is_unclosed = !has_valid_block_closing(block_node->opening_loc, block_node->closing_loc);
116
115
 
117
- if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) {
116
+ if (has_opening && is_unclosed) {
118
117
  current_type = CONTROL_TYPE_BLOCK;
119
118
  keyword_offset = (uint32_t) (node->location.start - context->source_start);
120
119
  }
@@ -125,11 +124,10 @@ static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* da
125
124
  case PM_LAMBDA_NODE: {
126
125
  pm_lambda_node_t* lambda = (pm_lambda_node_t*) node;
127
126
 
128
- bool has_do_opening = is_do_block(lambda->opening_loc);
129
- bool has_brace_opening = is_brace_block(lambda->opening_loc);
130
- bool has_valid_brace_closing = is_closing_brace(lambda->closing_loc);
127
+ bool has_opening = is_do_block(lambda->opening_loc) || is_brace_block(lambda->opening_loc);
128
+ bool is_unclosed = !has_valid_block_closing(lambda->opening_loc, lambda->closing_loc);
131
129
 
132
- if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) {
130
+ if (has_opening && is_unclosed) {
133
131
  current_type = CONTROL_TYPE_BLOCK;
134
132
  keyword_offset = (uint32_t) (node->location.start - context->source_start);
135
133
  }
@@ -5,6 +5,7 @@
5
5
  #include "../include/analyze/helpers.h"
6
6
  #include "../include/ast/ast_nodes.h"
7
7
  #include "../include/errors.h"
8
+ #include "../include/lexer/token.h"
8
9
  #include "../include/lib/hb_allocator.h"
9
10
  #include "../include/lib/hb_array.h"
10
11
  #include "../include/lib/hb_string.h"
@@ -811,6 +812,7 @@ static AST_ERB_RENDER_NODE_T* try_transform_content_node(
811
812
  ) {
812
813
  if (!erb_node->analyzed_ruby || !erb_node->analyzed_ruby->valid || !erb_node->analyzed_ruby->parsed) { return NULL; }
813
814
  if (is_erb_comment_tag(erb_node->tag_opening)) { return NULL; }
815
+ if (token_is_escaped_erb_tag_opening(erb_node->tag_opening)) { return NULL; }
814
816
 
815
817
  pm_call_node_t* render_call = find_render_call(erb_node->analyzed_ruby->root, &erb_node->analyzed_ruby->parser);
816
818
  if (!render_call) { return NULL; }
@@ -840,6 +842,7 @@ static AST_ERB_RENDER_NODE_T* try_transform_block_node(
840
842
  analyze_ruby_context_T* context
841
843
  ) {
842
844
  if (!block_node->content || hb_string_is_empty(block_node->content->value)) { return NULL; }
845
+ if (token_is_escaped_erb_tag_opening(block_node->tag_opening)) { return NULL; }
843
846
 
844
847
  const char* ruby_source = block_node->content->value.data;
845
848
  size_t ruby_length = block_node->content->value.length;
data/src/errors.c CHANGED
@@ -1248,6 +1248,33 @@ void append_dot_notation_casing_error(token_T* segment, position_T start, positi
1248
1248
  hb_array_append(errors, dot_notation_casing_error_init(segment, start, end, allocator));
1249
1249
  }
1250
1250
 
1251
+ TIMEOUT_ERROR_T* timeout_error_init(size_t timeout_ms, position_T start, position_T end, hb_allocator_T* allocator) {
1252
+ TIMEOUT_ERROR_T* timeout_error = hb_allocator_alloc(allocator, sizeof(TIMEOUT_ERROR_T));
1253
+
1254
+ if (!timeout_error) { return NULL; }
1255
+
1256
+ error_init(&timeout_error->base, TIMEOUT_ERROR, start, end);
1257
+
1258
+ const char* message_template = "Parsing timed out after %zu ms.";
1259
+
1260
+ char message[52];
1261
+ snprintf(
1262
+ message,
1263
+ sizeof(message),
1264
+ message_template,
1265
+ timeout_ms
1266
+ );
1267
+
1268
+ timeout_error->base.message = hb_string_copy(hb_string(message), allocator);
1269
+
1270
+ timeout_error->timeout_ms = timeout_ms;
1271
+ return timeout_error;
1272
+ }
1273
+
1274
+ void append_timeout_error(size_t timeout_ms, position_T start, position_T end, hb_allocator_T* allocator, hb_array_T* errors) {
1275
+ hb_array_append(errors, timeout_error_init(timeout_ms, start, end, allocator));
1276
+ }
1277
+
1251
1278
  hb_string_T error_type_to_string(ERROR_T* error) {
1252
1279
  switch (error->type) {
1253
1280
  case UNEXPECTED_ERROR: return hb_string("UNEXPECTED_ERROR");
@@ -1287,6 +1314,7 @@ hb_string_T error_type_to_string(ERROR_T* error) {
1287
1314
  case STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR: return hb_string("STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR");
1288
1315
  case VOID_ELEMENT_CONTENT_ERROR: return hb_string("VOID_ELEMENT_CONTENT_ERROR");
1289
1316
  case DOT_NOTATION_CASING_ERROR: return hb_string("DOT_NOTATION_CASING_ERROR");
1317
+ case TIMEOUT_ERROR: return hb_string("TIMEOUT_ERROR");
1290
1318
  }
1291
1319
 
1292
1320
  return hb_string("Unknown error_type_T");
@@ -1331,6 +1359,7 @@ hb_string_T error_human_type(ERROR_T* error) {
1331
1359
  case STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR: return hb_string("StrictLocalsDuplicateDeclarationError");
1332
1360
  case VOID_ELEMENT_CONTENT_ERROR: return hb_string("VoidElementContentError");
1333
1361
  case DOT_NOTATION_CASING_ERROR: return hb_string("DotNotationCasingError");
1362
+ case TIMEOUT_ERROR: return hb_string("TimeoutError");
1334
1363
  }
1335
1364
 
1336
1365
  return hb_string("Unknown error_type_T");
@@ -1588,6 +1617,12 @@ static void error_free_dot_notation_casing_error(DOT_NOTATION_CASING_ERROR_T* do
1588
1617
  error_free_base_error(&dot_notation_casing_error->base, allocator);
1589
1618
  }
1590
1619
 
1620
+ static void error_free_timeout_error(TIMEOUT_ERROR_T* timeout_error, hb_allocator_T* allocator) {
1621
+ // size_t is part of struct
1622
+
1623
+ error_free_base_error(&timeout_error->base, allocator);
1624
+ }
1625
+
1591
1626
  void error_free(ERROR_T* error, hb_allocator_T* allocator) {
1592
1627
  if (!error) { return; }
1593
1628
 
@@ -1629,6 +1664,7 @@ void error_free(ERROR_T* error, hb_allocator_T* allocator) {
1629
1664
  case STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR: error_free_strict_locals_duplicate_declaration_error((STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR_T*) error, allocator); break;
1630
1665
  case VOID_ELEMENT_CONTENT_ERROR: error_free_void_element_content_error((VOID_ELEMENT_CONTENT_ERROR_T*) error, allocator); break;
1631
1666
  case DOT_NOTATION_CASING_ERROR: error_free_dot_notation_casing_error((DOT_NOTATION_CASING_ERROR_T*) error, allocator); break;
1667
+ case TIMEOUT_ERROR: error_free_timeout_error((TIMEOUT_ERROR_T*) error, allocator); break;
1632
1668
  }
1633
1669
  }
1634
1670
 
@@ -2212,6 +2248,20 @@ static void error_pretty_print_dot_notation_casing_error(DOT_NOTATION_CASING_ERR
2212
2248
  pretty_print_token_property(error->segment, hb_string("segment"), indent, relative_indent, true, buffer);
2213
2249
  }
2214
2250
 
2251
+ static void error_pretty_print_timeout_error(TIMEOUT_ERROR_T* error, const size_t indent, const size_t relative_indent, hb_buffer_T* buffer) {
2252
+ if (!error) { return; }
2253
+
2254
+ hb_buffer_append(buffer, "@ ");
2255
+ hb_buffer_append_string(buffer, error_human_type((ERROR_T*) error));
2256
+ hb_buffer_append(buffer, " ");
2257
+
2258
+ pretty_print_location(error->base.location, buffer);
2259
+ hb_buffer_append(buffer, "\n");
2260
+
2261
+ pretty_print_quoted_property(hb_string("message"), error->base.message, indent, relative_indent, false, buffer);
2262
+ pretty_print_size_t_property(error->timeout_ms, hb_string("timeout_ms"), indent, relative_indent, true, buffer);
2263
+ }
2264
+
2215
2265
  void error_pretty_print(ERROR_T* error, const size_t indent, const size_t relative_indent, hb_buffer_T* buffer) {
2216
2266
  if (!error) { return; }
2217
2267
 
@@ -2253,6 +2303,7 @@ void error_pretty_print(ERROR_T* error, const size_t indent, const size_t relati
2253
2303
  case STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR: error_pretty_print_strict_locals_duplicate_declaration_error((STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR_T*) error, indent, relative_indent, buffer); break;
2254
2304
  case VOID_ELEMENT_CONTENT_ERROR: error_pretty_print_void_element_content_error((VOID_ELEMENT_CONTENT_ERROR_T*) error, indent, relative_indent, buffer); break;
2255
2305
  case DOT_NOTATION_CASING_ERROR: error_pretty_print_dot_notation_casing_error((DOT_NOTATION_CASING_ERROR_T*) error, indent, relative_indent, buffer); break;
2306
+ case TIMEOUT_ERROR: error_pretty_print_timeout_error((TIMEOUT_ERROR_T*) error, indent, relative_indent, buffer); break;
2256
2307
  }
2257
2308
  }
2258
2309
 
data/src/herb.c CHANGED
@@ -1,6 +1,7 @@
1
1
  #include "include/herb.h"
2
2
  #include "include/analyze/analyze.h"
3
3
  #include "include/analyze/prism_annotate.h"
4
+ #include "include/errors.h"
4
5
  #include "include/lexer/lexer.h"
5
6
  #include "include/lexer/token.h"
6
7
  #include "include/lib/hb_allocator.h"
@@ -44,6 +45,11 @@ HERB_EXPORTED_FUNCTION AST_DOCUMENT_NODE_T* herb_parse(
44
45
  parser_options_T parser_options = HERB_DEFAULT_PARSER_OPTIONS;
45
46
  if (options != NULL) { parser_options = *options; }
46
47
 
48
+ uint32_t error_count = 0;
49
+ parser_options.error_count = &error_count;
50
+
51
+ parser_options_set_deadline(&parser_options);
52
+
47
53
  if (parser_options.start_line > 0) {
48
54
  lexer.current_line = parser_options.start_line;
49
55
  lexer.previous_line = parser_options.start_line;
@@ -73,6 +79,16 @@ HERB_EXPORTED_FUNCTION AST_DOCUMENT_NODE_T* herb_parse(
73
79
  );
74
80
  }
75
81
 
82
+ if (parser_options_past_deadline(&parser_options)) {
83
+ append_timeout_error(
84
+ parser_options.timeout_ms,
85
+ document->base.location.start,
86
+ document->base.location.end,
87
+ allocator,
88
+ document->base.errors
89
+ );
90
+ }
91
+
76
92
  return document;
77
93
  }
78
94
 
data/src/include/errors.h CHANGED
@@ -51,6 +51,7 @@ typedef enum {
51
51
  STRICT_LOCALS_DUPLICATE_DECLARATION_ERROR,
52
52
  VOID_ELEMENT_CONTENT_ERROR,
53
53
  DOT_NOTATION_CASING_ERROR,
54
+ TIMEOUT_ERROR,
54
55
  } error_type_T;
55
56
 
56
57
  typedef struct ERROR_STRUCT {
@@ -267,6 +268,11 @@ typedef struct {
267
268
  token_T* segment;
268
269
  } DOT_NOTATION_CASING_ERROR_T;
269
270
 
271
+ typedef struct {
272
+ ERROR_T base;
273
+ size_t timeout_ms;
274
+ } TIMEOUT_ERROR_T;
275
+
270
276
  UNEXPECTED_ERROR_T* unexpected_error_init(hb_string_T description, hb_string_T expected, hb_string_T found, position_T start, position_T end, hb_allocator_T* allocator);
271
277
  void append_unexpected_error(hb_string_T description, hb_string_T expected, hb_string_T found, position_T start, position_T end, hb_allocator_T* allocator, hb_array_T* errors);
272
278
  UNEXPECTED_TOKEN_ERROR_T* unexpected_token_error_init(token_type_T expected_type, token_T* found, position_T start, position_T end, hb_allocator_T* allocator);
@@ -341,6 +347,8 @@ VOID_ELEMENT_CONTENT_ERROR_T* void_element_content_error_init(token_T* tag_name,
341
347
  void append_void_element_content_error(token_T* tag_name, hb_string_T helper_name, hb_string_T content_type, position_T start, position_T end, hb_allocator_T* allocator, hb_array_T* errors);
342
348
  DOT_NOTATION_CASING_ERROR_T* dot_notation_casing_error_init(token_T* segment, position_T start, position_T end, hb_allocator_T* allocator);
343
349
  void append_dot_notation_casing_error(token_T* segment, position_T start, position_T end, hb_allocator_T* allocator, hb_array_T* errors);
350
+ TIMEOUT_ERROR_T* timeout_error_init(size_t timeout_ms, position_T start, position_T end, hb_allocator_T* allocator);
351
+ void append_timeout_error(size_t timeout_ms, position_T start, position_T end, hb_allocator_T* allocator, hb_array_T* errors);
344
352
 
345
353
  void error_init(ERROR_T* error, error_type_T type, position_T start, position_T end);
346
354
 
@@ -28,4 +28,6 @@ void token_free(token_T* token, hb_allocator_T* allocator);
28
28
 
29
29
  bool token_value_empty(const token_T* token);
30
30
 
31
+ bool token_is_escaped_erb_tag_opening(const token_T* token);
32
+
31
33
  #endif
@@ -0,0 +1,15 @@
1
+ #ifndef HERB_CLOCK_H
2
+ #define HERB_CLOCK_H
3
+
4
+ #include <stdint.h>
5
+ #include <time.h>
6
+
7
+ static inline uint64_t hb_monotonic_ms(void) {
8
+ struct timespec now;
9
+
10
+ clock_gettime(CLOCK_MONOTONIC, &now);
11
+
12
+ return (uint64_t) now.tv_sec * 1000 + (uint64_t) now.tv_nsec / 1000000;
13
+ }
14
+
15
+ #endif
@@ -5,6 +5,7 @@
5
5
  #include "../lexer/lexer.h"
6
6
  #include "../lib/hb_allocator.h"
7
7
  #include "../lib/hb_array.h"
8
+ #include "../lib/hb_clock.h"
8
9
 
9
10
  #include <stdint.h>
10
11
 
@@ -33,6 +34,10 @@ typedef struct PARSER_OPTIONS_STRUCT {
33
34
  bool html;
34
35
  uint32_t start_line;
35
36
  uint32_t start_column;
37
+ uint32_t timeout_ms;
38
+ uint32_t max_errors;
39
+ uint32_t* error_count;
40
+ uint64_t deadline_ms;
36
41
  } parser_options_T;
37
42
 
38
43
  typedef struct MATCH_TAGS_CONTEXT_STRUCT {
@@ -43,6 +48,29 @@ typedef struct MATCH_TAGS_CONTEXT_STRUCT {
43
48
 
44
49
  extern const parser_options_T HERB_DEFAULT_PARSER_OPTIONS;
45
50
 
51
+ static inline bool parser_options_past_deadline(const parser_options_T* options) {
52
+ if (options == NULL || options->timeout_ms == 0) { return false; }
53
+
54
+ return hb_monotonic_ms() >= options->deadline_ms;
55
+ }
56
+
57
+ static inline bool parser_options_errors_exceeded(const parser_options_T* options) {
58
+ if (options == NULL || options->error_count == NULL) { return false; }
59
+ if (options->max_errors == 0) { return false; }
60
+
61
+ return *options->error_count >= options->max_errors;
62
+ }
63
+
64
+ static inline void parser_options_increment_error_count(const parser_options_T* options) {
65
+ if (options != NULL && options->error_count != NULL) { (*options->error_count)++; }
66
+ }
67
+
68
+ static inline void parser_options_set_deadline(parser_options_T* options) {
69
+ if (options->timeout_ms == 0) { return; }
70
+
71
+ options->deadline_ms = hb_monotonic_ms() + options->timeout_ms;
72
+ }
73
+
46
74
  typedef struct PARSER_STRUCT {
47
75
  hb_allocator_T* allocator;
48
76
  lexer_T* lexer;
@@ -1,6 +1,6 @@
1
1
  #ifndef HERB_VERSION_H
2
2
  #define HERB_VERSION_H
3
3
 
4
- #define HERB_VERSION "0.10.1"
4
+ #define HERB_VERSION "0.10.3"
5
5
 
6
6
  #endif
data/src/lexer/token.c CHANGED
@@ -228,6 +228,12 @@ bool token_value_empty(const token_T* token) {
228
228
  return token == NULL || hb_string_is_empty(token->value);
229
229
  }
230
230
 
231
+ bool token_is_escaped_erb_tag_opening(const token_T* token) {
232
+ if (token_value_empty(token)) { return false; }
233
+
234
+ return hb_string_starts_with(token->value, hb_string("<%%"));
235
+ }
236
+
231
237
  void token_free(token_T* token, hb_allocator_T* allocator) {
232
238
  if (!token) { return; }
233
239
 
data/src/parser.c CHANGED
@@ -23,7 +23,6 @@
23
23
  #include <strings.h>
24
24
 
25
25
  #define MAX_CONSECUTIVE_ERRORS 10
26
-
27
26
  static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T* errors);
28
27
  static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_array_T* errors);
29
28
  static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser);
@@ -48,7 +47,10 @@ const parser_options_T HERB_DEFAULT_PARSER_OPTIONS = { .track_whitespace = false
48
47
  .dot_notation_tags = false,
49
48
  .html = true,
50
49
  .start_line = 0,
51
- .start_column = 0 };
50
+ .start_column = 0,
51
+ .timeout_ms = 1000,
52
+ .max_errors = 25,
53
+ .deadline_ms = 0 };
52
54
 
53
55
  size_t parser_sizeof(void) {
54
56
  return sizeof(struct PARSER_STRUCT);
@@ -1498,6 +1500,7 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h
1498
1500
  }
1499
1501
 
1500
1502
  while (token_is_not(parser, TOKEN_EOF)) {
1503
+ if (parser_options_past_deadline(&parser->options)) { break; }
1501
1504
 
1502
1505
  if (token_is(parser, TOKEN_ERB_START)) {
1503
1506
  hb_array_append(children, parser_parse_erb_tag(parser));
@@ -1601,11 +1604,18 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h
1601
1604
  }
1602
1605
  }
1603
1606
 
1604
- static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_string_T tag_name) {
1607
+ static size_t find_matching_close_tag(
1608
+ hb_array_T* nodes,
1609
+ size_t start_index,
1610
+ hb_string_T tag_name,
1611
+ const parser_options_T* options
1612
+ ) {
1605
1613
  int depth = 0;
1606
1614
 
1607
- for (size_t i = start_idx + 1; i < hb_array_size(nodes); i++) {
1608
- AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1615
+ for (size_t index = start_index + 1; index < hb_array_size(nodes); index++) {
1616
+ if (parser_options_past_deadline(options)) { return (size_t) -1; }
1617
+
1618
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
1609
1619
  if (node == NULL) { continue; }
1610
1620
 
1611
1621
  if (node->type == AST_HTML_OPEN_TAG_NODE) {
@@ -1616,7 +1626,7 @@ static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_st
1616
1626
  AST_HTML_CLOSE_TAG_NODE_T* close = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1617
1627
 
1618
1628
  if (hb_string_equals_case_insensitive(close->tag_name->value, tag_name)) {
1619
- if (depth == 0) { return i; }
1629
+ if (depth == 0) { return index; }
1620
1630
  depth--;
1621
1631
  }
1622
1632
  }
@@ -1625,23 +1635,23 @@ static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_st
1625
1635
  return (size_t) -1;
1626
1636
  }
1627
1637
 
1628
- static size_t find_implicit_close_index(hb_array_T* nodes, size_t start_idx, hb_string_T tag_name) {
1638
+ static size_t find_implicit_close_index(hb_array_T* nodes, size_t start_index, hb_string_T tag_name) {
1629
1639
  if (!has_optional_end_tag(tag_name)) { return (size_t) -1; }
1630
1640
 
1631
- for (size_t i = start_idx + 1; i < hb_array_size(nodes); i++) {
1632
- AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1641
+ for (size_t index = start_index + 1; index < hb_array_size(nodes); index++) {
1642
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
1633
1643
  if (node == NULL) { continue; }
1634
1644
 
1635
1645
  if (node->type == AST_HTML_OPEN_TAG_NODE) {
1636
1646
  AST_HTML_OPEN_TAG_NODE_T* open = (AST_HTML_OPEN_TAG_NODE_T*) node;
1637
1647
  hb_string_T next_tag_name = open->tag_name->value;
1638
1648
 
1639
- if (should_implicitly_close(tag_name, next_tag_name)) { return i; }
1649
+ if (should_implicitly_close(tag_name, next_tag_name)) { return index; }
1640
1650
  } else if (node->type == AST_HTML_CLOSE_TAG_NODE) {
1641
1651
  AST_HTML_CLOSE_TAG_NODE_T* close = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1642
1652
  hb_string_T close_tag_name = close->tag_name->value;
1643
1653
 
1644
- if (parent_closes_element(tag_name, close_tag_name)) { return i; }
1654
+ if (parent_closes_element(tag_name, close_tag_name)) { return index; }
1645
1655
  }
1646
1656
  }
1647
1657
 
@@ -1655,6 +1665,35 @@ static hb_array_T* parser_build_elements_from_tags(
1655
1665
  hb_allocator_T* allocator
1656
1666
  );
1657
1667
 
1668
+ static bool has_close_tag_for_name(hb_array_T* close_tag_names, hb_string_T tag_name) {
1669
+ for (size_t i = 0; i < hb_array_size(close_tag_names); i++) {
1670
+ hb_string_T* name = (hb_string_T*) hb_array_get(close_tag_names, i);
1671
+ if (name != NULL && hb_string_equals_case_insensitive(*name, tag_name)) { return true; }
1672
+ }
1673
+
1674
+ return false;
1675
+ }
1676
+
1677
+ static hb_array_T* collect_close_tag_names(hb_array_T* nodes, hb_allocator_T* allocator) {
1678
+ hb_array_T* close_tag_names = hb_array_init(16, allocator);
1679
+
1680
+ for (size_t i = 0; i < hb_array_size(nodes); i++) {
1681
+ AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
1682
+ if (node == NULL || node->type != AST_HTML_CLOSE_TAG_NODE) { continue; }
1683
+
1684
+ AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1685
+ hb_string_T name = close_tag->tag_name->value;
1686
+
1687
+ if (!has_close_tag_for_name(close_tag_names, name)) {
1688
+ hb_string_T* stored = hb_allocator_alloc(allocator, sizeof(hb_string_T));
1689
+ *stored = name;
1690
+ hb_array_append(close_tag_names, stored);
1691
+ }
1692
+ }
1693
+
1694
+ return close_tag_names;
1695
+ }
1696
+
1658
1697
  static hb_array_T* parser_build_elements_from_tags(
1659
1698
  hb_array_T* nodes,
1660
1699
  hb_array_T* errors,
@@ -1663,8 +1702,11 @@ static hb_array_T* parser_build_elements_from_tags(
1663
1702
  ) {
1664
1703
  bool strict = options ? options->strict : false;
1665
1704
  hb_array_T* result = hb_array_init(hb_array_size(nodes), allocator);
1705
+ hb_array_T* close_tag_names = collect_close_tag_names(nodes, allocator);
1666
1706
 
1667
1707
  for (size_t index = 0; index < hb_array_size(nodes); index++) {
1708
+ if (parser_options_past_deadline(options)) { break; }
1709
+
1668
1710
  AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
1669
1711
  if (node == NULL) { continue; }
1670
1712
 
@@ -1672,7 +1714,11 @@ static hb_array_T* parser_build_elements_from_tags(
1672
1714
  AST_HTML_OPEN_TAG_NODE_T* open_tag = (AST_HTML_OPEN_TAG_NODE_T*) node;
1673
1715
  hb_string_T tag_name = open_tag->tag_name->value;
1674
1716
 
1675
- size_t close_index = find_matching_close_tag(nodes, index, tag_name);
1717
+ size_t close_index = (size_t) -1;
1718
+
1719
+ if (has_close_tag_for_name(close_tag_names, tag_name)) {
1720
+ close_index = find_matching_close_tag(nodes, index, tag_name, options);
1721
+ }
1676
1722
 
1677
1723
  if (close_index == (size_t) -1) {
1678
1724
  size_t implicit_close_index = find_implicit_close_index(nodes, index, tag_name);
@@ -1732,7 +1778,7 @@ static hb_array_T* parser_build_elements_from_tags(
1732
1778
 
1733
1779
  index = implicit_close_index - 1;
1734
1780
  } else {
1735
- if (hb_array_size(open_tag->base.errors) == 0) {
1781
+ if (hb_array_size(open_tag->base.errors) == 0 && !parser_options_errors_exceeded(options)) {
1736
1782
  append_missing_closing_tag_error(
1737
1783
  open_tag->tag_name,
1738
1784
  open_tag->base.location.start,
@@ -1740,6 +1786,8 @@ static hb_array_T* parser_build_elements_from_tags(
1740
1786
  allocator,
1741
1787
  open_tag->base.errors
1742
1788
  );
1789
+
1790
+ parser_options_increment_error_count(options);
1743
1791
  }
1744
1792
 
1745
1793
  hb_array_append(result, node);
@@ -1779,7 +1827,7 @@ static hb_array_T* parser_build_elements_from_tags(
1779
1827
  AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node;
1780
1828
 
1781
1829
  if (!is_void_element(close_tag->tag_name->value)) {
1782
- if (hb_array_size(close_tag->base.errors) == 0) {
1830
+ if (hb_array_size(close_tag->base.errors) == 0 && !parser_options_errors_exceeded(options)) {
1783
1831
  append_missing_opening_tag_error(
1784
1832
  close_tag->tag_name,
1785
1833
  close_tag->base.location.start,
@@ -1787,6 +1835,8 @@ static hb_array_T* parser_build_elements_from_tags(
1787
1835
  allocator,
1788
1836
  close_tag->base.errors
1789
1837
  );
1838
+
1839
+ parser_options_increment_error_count(options);
1790
1840
  }
1791
1841
  }
1792
1842
 
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.10.1
4
+ version: 0.10.3
5
5
  platform: arm-linux-gnu
6
6
  authors:
7
7
  - Marco Roth
@@ -397,12 +397,11 @@ files:
397
397
  - sig/herb/visitor.rbs
398
398
  - sig/herb/warnings.rbs
399
399
  - sig/herb_c_extension.rbs
400
+ - sig/manifest.yaml
400
401
  - sig/rubyvm.rbs
401
402
  - sig/serialized.rbs
402
403
  - sig/serialized_ast_errors.rbs
403
404
  - sig/serialized_ast_nodes.rbs
404
- - sig/vendor/did_you_mean.rbs
405
- - sig/vendor/parallel.rbs
406
405
  - src/analyze/action_view/attribute_extraction_helpers.c
407
406
  - src/analyze/action_view/generated_handlers.c
408
407
  - src/analyze/action_view/generated_handlers.h
@@ -488,6 +487,7 @@ files:
488
487
  - src/include/lib/hb_arena_debug.h
489
488
  - src/include/lib/hb_array.h
490
489
  - src/include/lib/hb_buffer.h
490
+ - src/include/lib/hb_clock.h
491
491
  - src/include/lib/hb_foreach.h
492
492
  - src/include/lib/hb_narray.h
493
493
  - src/include/lib/hb_string.h
@@ -705,7 +705,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
705
705
  - !ruby/object:Gem::Version
706
706
  version: 3.3.22
707
707
  requirements: []
708
- rubygems_version: 4.0.3
708
+ rubygems_version: 4.0.6
709
709
  specification_version: 4
710
710
  summary: The modern HTML+ERB Toolchain
711
711
  test_files: []
@@ -1,6 +0,0 @@
1
- module DidYouMean
2
- class SpellChecker
3
- def initialize: (dictionary: Array[String]) -> void
4
- def correct: (String input) -> Array[String]
5
- end
6
- end
@@ -1,4 +0,0 @@
1
- module Parallel
2
- def self.map: [T, U] (Array[T], ?in_processes: Integer) { (T) -> U } -> Array[U]
3
- def self.processor_count: () -> Integer
4
- end