htmljs-parser 5.1.5 → 5.2.1

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.
package/README.md CHANGED
@@ -249,6 +249,18 @@ const parser = createParser({
249
249
  range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
250
250
  },
251
251
 
252
+ /**
253
+ * Called after the type arguments for a tag have been parsed.
254
+ *
255
+ * @example
256
+ * 1╭─ <foo<string>>
257
+ * │ │╰─ tagTypeArgs.value "string"
258
+ * ╰─ ╰─ tagTypeArgs "<string>"
259
+ */
260
+ onTagTypeArgs(range) {
261
+ range.value; // Another range that includes only the type arguments themselves and not the angle brackets.
262
+ },
263
+
252
264
  /**
253
265
  * Called after a tag variable has been parsed.
254
266
  *
@@ -273,6 +285,18 @@ const parser = createParser({
273
285
  range.value; // Another range that includes only the args themselves and not the outer parenthesis.
274
286
  },
275
287
 
288
+ /**
289
+ * Called after type parameters for the tag parameters have been parsed.
290
+ *
291
+ * @example
292
+ * 1╭─ <tag<T>|input: { name: T }|>
293
+ * │ │╰─ tagTypeParams.value
294
+ * ╰─ ╰─ tagTypeParams "<T>"
295
+ */
296
+ onTagTypeParams(range) {
297
+ range.value; // Another range that includes only the type params themselves and not the angle brackets.
298
+ },
299
+
276
300
  /**
277
301
  * Called after tag parameters have been parsed.
278
302
  *
@@ -334,6 +358,9 @@ const parser = createParser({
334
358
  * ╰─ ╰─ attrMethod "(ev) { foo(); }"
335
359
  */
336
360
  onAttrMethod(range) {
361
+ range.typeParams; // Another range which includes the type params for the method.
362
+ range.typeParams.value; // Another range which includes the type params without outer angle brackets.
363
+
337
364
  range.params; // Another range which includes the params for the method.
338
365
  range.params.value; // Another range which includes the method params without outer parenthesis.
339
366
 
package/dist/index.js CHANGED
@@ -57,6 +57,9 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode3) => {
57
57
  ErrorCode3[ErrorCode3["MISSING_TAG_VARIABLE"] = 23] = "MISSING_TAG_VARIABLE";
58
58
  ErrorCode3[ErrorCode3["RESERVED_TAG_NAME"] = 24] = "RESERVED_TAG_NAME";
59
59
  ErrorCode3[ErrorCode3["ROOT_TAG_ONLY"] = 25] = "ROOT_TAG_ONLY";
60
+ ErrorCode3[ErrorCode3["INVALID_TAG_PARAMS"] = 26] = "INVALID_TAG_PARAMS";
61
+ ErrorCode3[ErrorCode3["INVALID_TAG_TYPES"] = 27] = "INVALID_TAG_TYPES";
62
+ ErrorCode3[ErrorCode3["INVALID_ATTR_TYPE_PARAMS"] = 28] = "INVALID_ATTR_TYPE_PARAMS";
60
63
  return ErrorCode3;
61
64
  })(ErrorCode || {});
62
65
  var TagType = /* @__PURE__ */ ((TagType2) => {
@@ -102,6 +105,9 @@ function htmlEOF() {
102
105
  }
103
106
  }
104
107
  }
108
+ function matchesCloseAngleBracket(code) {
109
+ return code === 62 /* CLOSE_ANGLE_BRACKET */;
110
+ }
105
111
  function matchesCloseParen(code) {
106
112
  return code === 41 /* CLOSE_PAREN */;
107
113
  }
@@ -392,8 +398,9 @@ var TAG_STAGE = /* @__PURE__ */ ((TAG_STAGE2) => {
392
398
  TAG_STAGE2[TAG_STAGE2["UNKNOWN"] = 0] = "UNKNOWN";
393
399
  TAG_STAGE2[TAG_STAGE2["VAR"] = 1] = "VAR";
394
400
  TAG_STAGE2[TAG_STAGE2["ARGUMENT"] = 2] = "ARGUMENT";
395
- TAG_STAGE2[TAG_STAGE2["PARAMS"] = 3] = "PARAMS";
396
- TAG_STAGE2[TAG_STAGE2["ATTR_GROUP"] = 4] = "ATTR_GROUP";
401
+ TAG_STAGE2[TAG_STAGE2["TYPES"] = 3] = "TYPES";
402
+ TAG_STAGE2[TAG_STAGE2["PARAMS"] = 4] = "PARAMS";
403
+ TAG_STAGE2[TAG_STAGE2["ATTR_GROUP"] = 5] = "ATTR_GROUP";
397
404
  return TAG_STAGE2;
398
405
  })(TAG_STAGE || {});
399
406
  var OPEN_TAG = {
@@ -412,6 +419,8 @@ var OPEN_TAG = {
412
419
  hasShorthandId: false,
413
420
  hasArgs: false,
414
421
  hasAttrs: false,
422
+ hasParams: false,
423
+ types: void 0,
415
424
  selfClosed: false,
416
425
  shorthandEnd: -1,
417
426
  tagName: void 0,
@@ -449,13 +458,13 @@ var OPEN_TAG = {
449
458
  }
450
459
  },
451
460
  eol(_, tag) {
452
- if (this.isConcise && tag.stage !== 4 /* ATTR_GROUP */) {
461
+ if (this.isConcise && tag.stage !== 5 /* ATTR_GROUP */) {
453
462
  this.exitState();
454
463
  }
455
464
  },
456
465
  eof(tag) {
457
466
  if (this.isConcise) {
458
- if (tag.stage === 4 /* ATTR_GROUP */) {
467
+ if (tag.stage === 5 /* ATTR_GROUP */) {
459
468
  this.emitError(
460
469
  tag,
461
470
  19 /* MALFORMED_OPEN_TAG */,
@@ -516,7 +525,7 @@ var OPEN_TAG = {
516
525
  );
517
526
  return;
518
527
  }
519
- if (tag.stage === 4 /* ATTR_GROUP */) {
528
+ if (tag.stage === 5 /* ATTR_GROUP */) {
520
529
  this.emitError(
521
530
  this.pos,
522
531
  19 /* MALFORMED_OPEN_TAG */,
@@ -545,7 +554,7 @@ var OPEN_TAG = {
545
554
  this.enterState(states_exports.BEGIN_DELIMITED_HTML_BLOCK);
546
555
  return;
547
556
  } else if (code === 91 /* OPEN_SQUARE_BRACKET */) {
548
- if (tag.stage === 4 /* ATTR_GROUP */) {
557
+ if (tag.stage === 5 /* ATTR_GROUP */) {
549
558
  this.emitError(
550
559
  this.pos,
551
560
  19 /* MALFORMED_OPEN_TAG */,
@@ -553,10 +562,10 @@ var OPEN_TAG = {
553
562
  );
554
563
  return;
555
564
  }
556
- tag.stage = 4 /* ATTR_GROUP */;
565
+ tag.stage = 5 /* ATTR_GROUP */;
557
566
  return;
558
567
  } else if (code === 93 /* CLOSE_SQUARE_BRACKET */) {
559
- if (tag.stage !== 4 /* ATTR_GROUP */) {
568
+ if (tag.stage !== 5 /* ATTR_GROUP */) {
560
569
  this.emitError(
561
570
  this.pos,
562
571
  19 /* MALFORMED_OPEN_TAG */,
@@ -577,13 +586,6 @@ var OPEN_TAG = {
577
586
  this.exitState();
578
587
  return;
579
588
  }
580
- if (code === 60 /* OPEN_ANGLE_BRACKET */) {
581
- return this.emitError(
582
- this.pos,
583
- 2 /* INVALID_ATTRIBUTE_NAME */,
584
- 'Invalid attribute name. Attribute name cannot begin with the "<" character.'
585
- );
586
- }
587
589
  if (code === 47 /* FORWARD_SLASH */) {
588
590
  switch (this.lookAtCharCodeAhead(1)) {
589
591
  case 47 /* FORWARD_SLASH */:
@@ -601,113 +603,171 @@ var OPEN_TAG = {
601
603
  this.pos++;
602
604
  this.forward = 0;
603
605
  this.consumeWhitespace();
604
- } else if (code === 47 /* FORWARD_SLASH */ && !tag.hasAttrs) {
605
- tag.stage = 1 /* VAR */;
606
- this.pos++;
607
- this.forward = 0;
608
- if (isWhitespaceCode(this.lookAtCharCodeAhead(0))) {
609
- return this.emitError(
610
- this.pos,
611
- 23 /* MISSING_TAG_VARIABLE */,
612
- "A slash was found that was not followed by a variable name or lhs expression"
613
- );
614
- }
615
- const expr = this.enterState(states_exports.EXPRESSION);
616
- expr.operators = true;
617
- expr.terminatedByWhitespace = true;
618
- expr.shouldTerminate = this.isConcise ? shouldTerminateConciseTagVar : shouldTerminateHtmlTagVar;
619
- } else if (code === 40 /* OPEN_PAREN */ && !tag.hasAttrs) {
620
- if (tag.hasArgs) {
621
- this.emitError(
622
- this.pos,
623
- 11 /* INVALID_TAG_ARGUMENT */,
624
- "A tag can only have one argument"
625
- );
626
- return;
627
- }
628
- tag.stage = 2 /* ARGUMENT */;
629
- this.pos++;
630
- this.forward = 0;
631
- this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen;
632
- } else if (code === 124 /* PIPE */ && !tag.hasAttrs) {
633
- tag.stage = 3 /* PARAMS */;
634
- this.pos++;
635
- this.forward = 0;
636
- this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesPipe;
637
606
  } else {
638
607
  this.forward = 0;
639
- if (tag.tagName) {
608
+ if (tag.hasAttrs) {
640
609
  this.enterState(states_exports.ATTRIBUTE);
641
- tag.hasAttrs = true;
642
- } else {
643
- this.enterState(states_exports.TAG_NAME);
644
- }
645
- }
646
- },
647
- return(child, tag) {
648
- var _a, _b, _c, _d, _e, _f;
649
- switch (child.state) {
650
- case states_exports.JS_COMMENT_BLOCK: {
651
- break;
652
- }
653
- case states_exports.EXPRESSION: {
654
- switch (tag.stage) {
655
- case 1 /* VAR */: {
656
- if (child.start === child.end) {
610
+ } else if (tag.tagName) {
611
+ switch (code) {
612
+ case 47 /* FORWARD_SLASH */: {
613
+ tag.stage = 1 /* VAR */;
614
+ this.pos++;
615
+ if (isWhitespaceCode(this.lookAtCharCodeAhead(0))) {
657
616
  return this.emitError(
658
- child,
617
+ this.pos,
659
618
  23 /* MISSING_TAG_VARIABLE */,
660
619
  "A slash was found that was not followed by a variable name or lhs expression"
661
620
  );
662
621
  }
663
- (_b = (_a = this.options).onTagVar) == null ? void 0 : _b.call(_a, {
664
- start: child.start - 1,
665
- end: child.end,
666
- value: {
667
- start: child.start,
668
- end: child.end
669
- }
670
- });
622
+ const expr = this.enterState(states_exports.EXPRESSION);
623
+ expr.operators = true;
624
+ expr.terminatedByWhitespace = true;
625
+ expr.shouldTerminate = this.isConcise ? shouldTerminateConciseTagVar : shouldTerminateHtmlTagVar;
671
626
  break;
672
627
  }
673
- case 2 /* ARGUMENT */: {
674
- const start = child.start - 1;
675
- const end = ++this.pos;
676
- const value = {
677
- start: child.start,
678
- end: child.end
679
- };
680
- if (this.consumeWhitespaceIfBefore("{")) {
681
- const attr = this.enterState(states_exports.ATTRIBUTE);
682
- attr.start = start;
683
- attr.args = { start, end, value };
684
- tag.hasAttrs = true;
685
- this.forward = 0;
686
- } else {
687
- tag.hasArgs = true;
688
- (_d = (_c = this.options).onTagArgs) == null ? void 0 : _d.call(_c, {
689
- start,
690
- end,
691
- value
692
- });
628
+ case 40 /* OPEN_PAREN */:
629
+ if (tag.hasArgs) {
630
+ this.emitError(
631
+ this.pos,
632
+ 11 /* INVALID_TAG_ARGUMENT */,
633
+ "A tag can only have one argument"
634
+ );
635
+ return;
693
636
  }
637
+ tag.hasArgs = true;
638
+ tag.stage = 2 /* ARGUMENT */;
639
+ this.pos++;
640
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen;
694
641
  break;
695
- }
696
- case 3 /* PARAMS */: {
697
- const end = ++this.pos;
698
- (_f = (_e = this.options).onTagParams) == null ? void 0 : _f.call(_e, {
699
- start: child.start - 1,
700
- end,
701
- value: {
702
- start: child.start,
703
- end: child.end
704
- }
705
- });
642
+ case 124 /* PIPE */:
643
+ if (tag.hasParams) {
644
+ this.emitError(
645
+ this.pos,
646
+ 26 /* INVALID_TAG_PARAMS */,
647
+ "A tag can only specify parameters once"
648
+ );
649
+ return;
650
+ }
651
+ tag.hasParams = true;
652
+ tag.stage = 4 /* PARAMS */;
653
+ this.pos++;
654
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesPipe;
655
+ break;
656
+ case 60 /* OPEN_ANGLE_BRACKET */:
657
+ tag.stage = 3 /* TYPES */;
658
+ this.pos++;
659
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseAngleBracket;
706
660
  break;
661
+ default:
662
+ tag.hasAttrs = true;
663
+ this.enterState(states_exports.ATTRIBUTE);
664
+ }
665
+ } else {
666
+ this.enterState(states_exports.TAG_NAME);
667
+ }
668
+ }
669
+ },
670
+ return(child, tag) {
671
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
672
+ if (child.state !== states_exports.EXPRESSION)
673
+ return;
674
+ switch (tag.stage) {
675
+ case 1 /* VAR */: {
676
+ if (child.start === child.end) {
677
+ return this.emitError(
678
+ child,
679
+ 23 /* MISSING_TAG_VARIABLE */,
680
+ "A slash was found that was not followed by a variable name or lhs expression"
681
+ );
682
+ }
683
+ (_b = (_a = this.options).onTagVar) == null ? void 0 : _b.call(_a, {
684
+ start: child.start - 1,
685
+ end: child.end,
686
+ value: {
687
+ start: child.start,
688
+ end: child.end
689
+ }
690
+ });
691
+ break;
692
+ }
693
+ case 2 /* ARGUMENT */: {
694
+ const { types } = tag;
695
+ const start = child.start - 1;
696
+ const end = ++this.pos;
697
+ const value = {
698
+ start: child.start,
699
+ end: child.end
700
+ };
701
+ if (this.consumeWhitespaceIfBefore("{")) {
702
+ const attr = this.enterState(states_exports.ATTRIBUTE);
703
+ if (types) {
704
+ attr.start = types.start;
705
+ attr.typeParams = types;
706
+ } else {
707
+ attr.start = start;
708
+ }
709
+ attr.args = { start, end, value };
710
+ this.forward = 0;
711
+ tag.hasAttrs = true;
712
+ } else {
713
+ if (types) {
714
+ (_d = (_c = this.options).onTagTypeArgs) == null ? void 0 : _d.call(_c, types);
715
+ }
716
+ (_f = (_e = this.options).onTagArgs) == null ? void 0 : _f.call(_e, {
717
+ start,
718
+ end,
719
+ value
720
+ });
721
+ }
722
+ break;
723
+ }
724
+ case 3 /* TYPES */: {
725
+ const { types, hasParams, hasArgs } = tag;
726
+ const end = ++this.pos;
727
+ const typeArgs = {
728
+ start: child.start - 1,
729
+ end,
730
+ value: {
731
+ start: child.start,
732
+ end: child.end
707
733
  }
734
+ };
735
+ this.consumeWhitespace();
736
+ const nextCode = this.lookAtCharCodeAhead(0);
737
+ if (!hasParams && nextCode === 124 /* PIPE */) {
738
+ if (types) {
739
+ (_h = (_g = this.options).onTagTypeArgs) == null ? void 0 : _h.call(_g, types);
740
+ }
741
+ (_j = (_i = this.options).onTagTypeParams) == null ? void 0 : _j.call(_i, typeArgs);
742
+ } else if (!hasArgs && nextCode === 40 /* OPEN_PAREN */) {
743
+ if (types) {
744
+ (_l = (_k = this.options).onTagTypeArgs) == null ? void 0 : _l.call(_k, types);
745
+ }
746
+ tag.types = typeArgs;
747
+ } else if (!(types || hasParams || hasArgs)) {
748
+ (_n = (_m = this.options).onTagTypeArgs) == null ? void 0 : _n.call(_m, typeArgs);
749
+ tag.types = typeArgs;
750
+ } else {
751
+ this.emitError(
752
+ child,
753
+ 27 /* INVALID_TAG_TYPES */,
754
+ "Unexpected types. Type arguments must follow a tag name and type paremeters must precede a method or tag parameters."
755
+ );
708
756
  }
709
757
  break;
710
758
  }
759
+ case 4 /* PARAMS */: {
760
+ const end = ++this.pos;
761
+ (_p = (_o = this.options).onTagParams) == null ? void 0 : _p.call(_o, {
762
+ start: child.start - 1,
763
+ end,
764
+ value: {
765
+ start: child.start,
766
+ end: child.end
767
+ }
768
+ });
769
+ break;
770
+ }
711
771
  }
712
772
  }
713
773
  };
@@ -718,6 +778,7 @@ function shouldTerminateConciseTagVar(code, data, pos) {
718
778
  case 124 /* PIPE */:
719
779
  case 40 /* OPEN_PAREN */:
720
780
  case 59 /* SEMICOLON */:
781
+ case 60 /* OPEN_ANGLE_BRACKET */:
721
782
  return true;
722
783
  case 58 /* COLON */:
723
784
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -732,6 +793,7 @@ function shouldTerminateHtmlTagVar(code, data, pos) {
732
793
  case 61 /* EQUAL */:
733
794
  case 40 /* OPEN_PAREN */:
734
795
  case 62 /* CLOSE_ANGLE_BRACKET */:
796
+ case 60 /* OPEN_ANGLE_BRACKET */:
735
797
  return true;
736
798
  case 58 /* COLON */:
737
799
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -755,6 +817,7 @@ var ATTRIBUTE = {
755
817
  stage: 0 /* UNKNOWN */,
756
818
  name: void 0,
757
819
  args: false,
820
+ typeParams: void 0,
758
821
  bound: false,
759
822
  spread: false
760
823
  };
@@ -785,25 +848,37 @@ var ATTRIBUTE = {
785
848
  const expr = this.enterState(states_exports.EXPRESSION);
786
849
  expr.operators = true;
787
850
  expr.terminatedByWhitespace = true;
788
- expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 4 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrValue : shouldTerminateConciseAttrValue : shouldTerminateHtmlAttrValue;
851
+ expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 5 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrValue : shouldTerminateConciseAttrValue : shouldTerminateHtmlAttrValue;
789
852
  } else if (code === 40 /* OPEN_PAREN */) {
790
853
  ensureAttrName(this, attr);
791
854
  attr.stage = 3 /* ARGUMENT */;
792
855
  this.pos++;
793
856
  this.forward = 0;
794
857
  this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen;
858
+ } else if (code === 60 /* OPEN_ANGLE_BRACKET */ && attr.stage === 1 /* NAME */) {
859
+ attr.stage = 4 /* TYPE_PARAMS */;
860
+ this.pos++;
861
+ this.forward = 0;
862
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseAngleBracket;
795
863
  } else if (code === 123 /* OPEN_CURLY_BRACE */ && attr.args) {
796
864
  ensureAttrName(this, attr);
797
- attr.stage = 4 /* BLOCK */;
865
+ attr.stage = 5 /* BLOCK */;
798
866
  this.pos++;
799
867
  this.forward = 0;
800
868
  this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace;
801
869
  } else if (attr.stage === 0 /* UNKNOWN */) {
870
+ if (code === 60 /* OPEN_ANGLE_BRACKET */) {
871
+ return this.emitError(
872
+ this.pos,
873
+ 2 /* INVALID_ATTRIBUTE_NAME */,
874
+ 'Invalid attribute name. Attribute name cannot begin with the "<" character.'
875
+ );
876
+ }
802
877
  attr.stage = 1 /* NAME */;
803
878
  this.forward = 0;
804
879
  const expr = this.enterState(states_exports.EXPRESSION);
805
880
  expr.terminatedByWhitespace = true;
806
- expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 4 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrName : shouldTerminateConciseAttrName : shouldTerminateHtmlAttrName;
881
+ expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 5 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrName : shouldTerminateConciseAttrName : shouldTerminateHtmlAttrName;
807
882
  } else {
808
883
  this.exitState();
809
884
  }
@@ -856,6 +931,12 @@ var ATTRIBUTE = {
856
931
  end,
857
932
  value
858
933
  };
934
+ } else if (attr.typeParams) {
935
+ this.emitError(
936
+ child,
937
+ 1 /* INVALID_ATTRIBUTE_ARGUMENT */,
938
+ "An attribute cannot have both type parameters and arguments"
939
+ );
859
940
  } else {
860
941
  attr.args = true;
861
942
  (_d = (_c = this.options).onAttrArgs) == null ? void 0 : _d.call(_c, {
@@ -866,14 +947,16 @@ var ATTRIBUTE = {
866
947
  }
867
948
  break;
868
949
  }
869
- case 4 /* BLOCK */: {
950
+ case 5 /* BLOCK */: {
870
951
  const params = attr.args;
871
- const start = params.start;
872
952
  const end = ++this.pos;
953
+ const { typeParams } = attr;
954
+ const start = typeParams ? typeParams.start : params.start;
873
955
  (_f = (_e = this.options).onAttrMethod) == null ? void 0 : _f.call(_e, {
874
956
  start,
875
957
  end,
876
958
  params,
959
+ typeParams,
877
960
  body: {
878
961
  start: child.start - 1,
879
962
  end,
@@ -886,6 +969,26 @@ var ATTRIBUTE = {
886
969
  this.exitState();
887
970
  break;
888
971
  }
972
+ case 4 /* TYPE_PARAMS */: {
973
+ const start = child.start - 1;
974
+ const end = ++this.pos;
975
+ if (!this.consumeWhitespaceIfBefore("(")) {
976
+ return this.emitError(
977
+ child,
978
+ 28 /* INVALID_ATTR_TYPE_PARAMS */,
979
+ "Attribute cannot contain type parameters unless it is a shorthand method"
980
+ );
981
+ }
982
+ attr.typeParams = {
983
+ start,
984
+ end,
985
+ value: {
986
+ start: child.start,
987
+ end: child.end
988
+ }
989
+ };
990
+ break;
991
+ }
889
992
  case 2 /* VALUE */: {
890
993
  if (child.start === child.end) {
891
994
  return this.emitError(
@@ -935,6 +1038,7 @@ function shouldTerminateHtmlAttrName(code, data, pos) {
935
1038
  case 61 /* EQUAL */:
936
1039
  case 40 /* OPEN_PAREN */:
937
1040
  case 62 /* CLOSE_ANGLE_BRACKET */:
1041
+ case 60 /* OPEN_ANGLE_BRACKET */:
938
1042
  return true;
939
1043
  case 58 /* COLON */:
940
1044
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -962,6 +1066,7 @@ function shouldTerminateConciseAttrName(code, data, pos) {
962
1066
  case 61 /* EQUAL */:
963
1067
  case 40 /* OPEN_PAREN */:
964
1068
  case 59 /* SEMICOLON */:
1069
+ case 60 /* OPEN_ANGLE_BRACKET */:
965
1070
  return true;
966
1071
  case 58 /* COLON */:
967
1072
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -988,6 +1093,7 @@ function shouldTerminateConciseGroupedAttrName(code, data, pos) {
988
1093
  case 61 /* EQUAL */:
989
1094
  case 40 /* OPEN_PAREN */:
990
1095
  case 93 /* CLOSE_SQUARE_BRACKET */:
1096
+ case 60 /* OPEN_ANGLE_BRACKET */:
991
1097
  return true;
992
1098
  case 58 /* COLON */:
993
1099
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -2211,6 +2317,7 @@ function checkForPlaceholder(parser, code) {
2211
2317
  curCode = parser.lookAtCharCodeAhead(ahead + 2);
2212
2318
  }
2213
2319
  if (curCode === 123 /* OPEN_CURLY_BRACE */) {
2320
+ parser.forward = 0;
2214
2321
  if (ahead) {
2215
2322
  const remainder = ahead % 2;
2216
2323
  const extra = (ahead + remainder) / 2;
@@ -2230,7 +2337,6 @@ function checkForPlaceholder(parser, code) {
2230
2337
  parser.endText();
2231
2338
  parser.enterState(PLACEHOLDER).escape = escape;
2232
2339
  parser.pos += escape ? 2 : 3;
2233
- parser.forward = 0;
2234
2340
  parser.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace;
2235
2341
  return true;
2236
2342
  }
@@ -2329,6 +2435,7 @@ var TAG_NAME = {
2329
2435
  parent,
2330
2436
  start,
2331
2437
  end: start,
2438
+ shorthandCode: -1,
2332
2439
  expressions: [],
2333
2440
  quasis: [{ start, end: start }]
2334
2441
  };
@@ -2408,7 +2515,7 @@ var TAG_NAME = {
2408
2515
  this.pos += 2;
2409
2516
  this.forward = 0;
2410
2517
  this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace;
2411
- } else if (isWhitespaceCode(code) || code === 61 /* EQUAL */ || code === 58 /* COLON */ && this.lookAtCharCodeAhead(1) === 61 /* EQUAL */ || code === 40 /* OPEN_PAREN */ || code === 47 /* FORWARD_SLASH */ || code === 124 /* PIPE */ || (this.isConcise ? code === 59 /* SEMICOLON */ : code === 62 /* CLOSE_ANGLE_BRACKET */)) {
2518
+ } else if (isWhitespaceCode(code) || code === 61 /* EQUAL */ || code === 58 /* COLON */ && this.lookAtCharCodeAhead(1) === 61 /* EQUAL */ || code === 40 /* OPEN_PAREN */ || code === 47 /* FORWARD_SLASH */ || code === 124 /* PIPE */ || code === 60 /* OPEN_ANGLE_BRACKET */ || (this.isConcise ? code === 59 /* SEMICOLON */ : code === 62 /* CLOSE_ANGLE_BRACKET */)) {
2412
2519
  this.activeTag.shorthandEnd = this.pos;
2413
2520
  this.exitState();
2414
2521
  } else if (code === 46 /* PERIOD */ || code === 35 /* NUMBER_SIGN */) {
package/dist/index.mjs CHANGED
@@ -32,6 +32,9 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode3) => {
32
32
  ErrorCode3[ErrorCode3["MISSING_TAG_VARIABLE"] = 23] = "MISSING_TAG_VARIABLE";
33
33
  ErrorCode3[ErrorCode3["RESERVED_TAG_NAME"] = 24] = "RESERVED_TAG_NAME";
34
34
  ErrorCode3[ErrorCode3["ROOT_TAG_ONLY"] = 25] = "ROOT_TAG_ONLY";
35
+ ErrorCode3[ErrorCode3["INVALID_TAG_PARAMS"] = 26] = "INVALID_TAG_PARAMS";
36
+ ErrorCode3[ErrorCode3["INVALID_TAG_TYPES"] = 27] = "INVALID_TAG_TYPES";
37
+ ErrorCode3[ErrorCode3["INVALID_ATTR_TYPE_PARAMS"] = 28] = "INVALID_ATTR_TYPE_PARAMS";
35
38
  return ErrorCode3;
36
39
  })(ErrorCode || {});
37
40
  var TagType = /* @__PURE__ */ ((TagType2) => {
@@ -77,6 +80,9 @@ function htmlEOF() {
77
80
  }
78
81
  }
79
82
  }
83
+ function matchesCloseAngleBracket(code) {
84
+ return code === 62 /* CLOSE_ANGLE_BRACKET */;
85
+ }
80
86
  function matchesCloseParen(code) {
81
87
  return code === 41 /* CLOSE_PAREN */;
82
88
  }
@@ -367,8 +373,9 @@ var TAG_STAGE = /* @__PURE__ */ ((TAG_STAGE2) => {
367
373
  TAG_STAGE2[TAG_STAGE2["UNKNOWN"] = 0] = "UNKNOWN";
368
374
  TAG_STAGE2[TAG_STAGE2["VAR"] = 1] = "VAR";
369
375
  TAG_STAGE2[TAG_STAGE2["ARGUMENT"] = 2] = "ARGUMENT";
370
- TAG_STAGE2[TAG_STAGE2["PARAMS"] = 3] = "PARAMS";
371
- TAG_STAGE2[TAG_STAGE2["ATTR_GROUP"] = 4] = "ATTR_GROUP";
376
+ TAG_STAGE2[TAG_STAGE2["TYPES"] = 3] = "TYPES";
377
+ TAG_STAGE2[TAG_STAGE2["PARAMS"] = 4] = "PARAMS";
378
+ TAG_STAGE2[TAG_STAGE2["ATTR_GROUP"] = 5] = "ATTR_GROUP";
372
379
  return TAG_STAGE2;
373
380
  })(TAG_STAGE || {});
374
381
  var OPEN_TAG = {
@@ -387,6 +394,8 @@ var OPEN_TAG = {
387
394
  hasShorthandId: false,
388
395
  hasArgs: false,
389
396
  hasAttrs: false,
397
+ hasParams: false,
398
+ types: void 0,
390
399
  selfClosed: false,
391
400
  shorthandEnd: -1,
392
401
  tagName: void 0,
@@ -424,13 +433,13 @@ var OPEN_TAG = {
424
433
  }
425
434
  },
426
435
  eol(_, tag) {
427
- if (this.isConcise && tag.stage !== 4 /* ATTR_GROUP */) {
436
+ if (this.isConcise && tag.stage !== 5 /* ATTR_GROUP */) {
428
437
  this.exitState();
429
438
  }
430
439
  },
431
440
  eof(tag) {
432
441
  if (this.isConcise) {
433
- if (tag.stage === 4 /* ATTR_GROUP */) {
442
+ if (tag.stage === 5 /* ATTR_GROUP */) {
434
443
  this.emitError(
435
444
  tag,
436
445
  19 /* MALFORMED_OPEN_TAG */,
@@ -491,7 +500,7 @@ var OPEN_TAG = {
491
500
  );
492
501
  return;
493
502
  }
494
- if (tag.stage === 4 /* ATTR_GROUP */) {
503
+ if (tag.stage === 5 /* ATTR_GROUP */) {
495
504
  this.emitError(
496
505
  this.pos,
497
506
  19 /* MALFORMED_OPEN_TAG */,
@@ -520,7 +529,7 @@ var OPEN_TAG = {
520
529
  this.enterState(states_exports.BEGIN_DELIMITED_HTML_BLOCK);
521
530
  return;
522
531
  } else if (code === 91 /* OPEN_SQUARE_BRACKET */) {
523
- if (tag.stage === 4 /* ATTR_GROUP */) {
532
+ if (tag.stage === 5 /* ATTR_GROUP */) {
524
533
  this.emitError(
525
534
  this.pos,
526
535
  19 /* MALFORMED_OPEN_TAG */,
@@ -528,10 +537,10 @@ var OPEN_TAG = {
528
537
  );
529
538
  return;
530
539
  }
531
- tag.stage = 4 /* ATTR_GROUP */;
540
+ tag.stage = 5 /* ATTR_GROUP */;
532
541
  return;
533
542
  } else if (code === 93 /* CLOSE_SQUARE_BRACKET */) {
534
- if (tag.stage !== 4 /* ATTR_GROUP */) {
543
+ if (tag.stage !== 5 /* ATTR_GROUP */) {
535
544
  this.emitError(
536
545
  this.pos,
537
546
  19 /* MALFORMED_OPEN_TAG */,
@@ -552,13 +561,6 @@ var OPEN_TAG = {
552
561
  this.exitState();
553
562
  return;
554
563
  }
555
- if (code === 60 /* OPEN_ANGLE_BRACKET */) {
556
- return this.emitError(
557
- this.pos,
558
- 2 /* INVALID_ATTRIBUTE_NAME */,
559
- 'Invalid attribute name. Attribute name cannot begin with the "<" character.'
560
- );
561
- }
562
564
  if (code === 47 /* FORWARD_SLASH */) {
563
565
  switch (this.lookAtCharCodeAhead(1)) {
564
566
  case 47 /* FORWARD_SLASH */:
@@ -576,113 +578,171 @@ var OPEN_TAG = {
576
578
  this.pos++;
577
579
  this.forward = 0;
578
580
  this.consumeWhitespace();
579
- } else if (code === 47 /* FORWARD_SLASH */ && !tag.hasAttrs) {
580
- tag.stage = 1 /* VAR */;
581
- this.pos++;
582
- this.forward = 0;
583
- if (isWhitespaceCode(this.lookAtCharCodeAhead(0))) {
584
- return this.emitError(
585
- this.pos,
586
- 23 /* MISSING_TAG_VARIABLE */,
587
- "A slash was found that was not followed by a variable name or lhs expression"
588
- );
589
- }
590
- const expr = this.enterState(states_exports.EXPRESSION);
591
- expr.operators = true;
592
- expr.terminatedByWhitespace = true;
593
- expr.shouldTerminate = this.isConcise ? shouldTerminateConciseTagVar : shouldTerminateHtmlTagVar;
594
- } else if (code === 40 /* OPEN_PAREN */ && !tag.hasAttrs) {
595
- if (tag.hasArgs) {
596
- this.emitError(
597
- this.pos,
598
- 11 /* INVALID_TAG_ARGUMENT */,
599
- "A tag can only have one argument"
600
- );
601
- return;
602
- }
603
- tag.stage = 2 /* ARGUMENT */;
604
- this.pos++;
605
- this.forward = 0;
606
- this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen;
607
- } else if (code === 124 /* PIPE */ && !tag.hasAttrs) {
608
- tag.stage = 3 /* PARAMS */;
609
- this.pos++;
610
- this.forward = 0;
611
- this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesPipe;
612
581
  } else {
613
582
  this.forward = 0;
614
- if (tag.tagName) {
583
+ if (tag.hasAttrs) {
615
584
  this.enterState(states_exports.ATTRIBUTE);
616
- tag.hasAttrs = true;
617
- } else {
618
- this.enterState(states_exports.TAG_NAME);
619
- }
620
- }
621
- },
622
- return(child, tag) {
623
- var _a, _b, _c, _d, _e, _f;
624
- switch (child.state) {
625
- case states_exports.JS_COMMENT_BLOCK: {
626
- break;
627
- }
628
- case states_exports.EXPRESSION: {
629
- switch (tag.stage) {
630
- case 1 /* VAR */: {
631
- if (child.start === child.end) {
585
+ } else if (tag.tagName) {
586
+ switch (code) {
587
+ case 47 /* FORWARD_SLASH */: {
588
+ tag.stage = 1 /* VAR */;
589
+ this.pos++;
590
+ if (isWhitespaceCode(this.lookAtCharCodeAhead(0))) {
632
591
  return this.emitError(
633
- child,
592
+ this.pos,
634
593
  23 /* MISSING_TAG_VARIABLE */,
635
594
  "A slash was found that was not followed by a variable name or lhs expression"
636
595
  );
637
596
  }
638
- (_b = (_a = this.options).onTagVar) == null ? void 0 : _b.call(_a, {
639
- start: child.start - 1,
640
- end: child.end,
641
- value: {
642
- start: child.start,
643
- end: child.end
644
- }
645
- });
597
+ const expr = this.enterState(states_exports.EXPRESSION);
598
+ expr.operators = true;
599
+ expr.terminatedByWhitespace = true;
600
+ expr.shouldTerminate = this.isConcise ? shouldTerminateConciseTagVar : shouldTerminateHtmlTagVar;
646
601
  break;
647
602
  }
648
- case 2 /* ARGUMENT */: {
649
- const start = child.start - 1;
650
- const end = ++this.pos;
651
- const value = {
652
- start: child.start,
653
- end: child.end
654
- };
655
- if (this.consumeWhitespaceIfBefore("{")) {
656
- const attr = this.enterState(states_exports.ATTRIBUTE);
657
- attr.start = start;
658
- attr.args = { start, end, value };
659
- tag.hasAttrs = true;
660
- this.forward = 0;
661
- } else {
662
- tag.hasArgs = true;
663
- (_d = (_c = this.options).onTagArgs) == null ? void 0 : _d.call(_c, {
664
- start,
665
- end,
666
- value
667
- });
603
+ case 40 /* OPEN_PAREN */:
604
+ if (tag.hasArgs) {
605
+ this.emitError(
606
+ this.pos,
607
+ 11 /* INVALID_TAG_ARGUMENT */,
608
+ "A tag can only have one argument"
609
+ );
610
+ return;
668
611
  }
612
+ tag.hasArgs = true;
613
+ tag.stage = 2 /* ARGUMENT */;
614
+ this.pos++;
615
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen;
669
616
  break;
670
- }
671
- case 3 /* PARAMS */: {
672
- const end = ++this.pos;
673
- (_f = (_e = this.options).onTagParams) == null ? void 0 : _f.call(_e, {
674
- start: child.start - 1,
675
- end,
676
- value: {
677
- start: child.start,
678
- end: child.end
679
- }
680
- });
617
+ case 124 /* PIPE */:
618
+ if (tag.hasParams) {
619
+ this.emitError(
620
+ this.pos,
621
+ 26 /* INVALID_TAG_PARAMS */,
622
+ "A tag can only specify parameters once"
623
+ );
624
+ return;
625
+ }
626
+ tag.hasParams = true;
627
+ tag.stage = 4 /* PARAMS */;
628
+ this.pos++;
629
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesPipe;
630
+ break;
631
+ case 60 /* OPEN_ANGLE_BRACKET */:
632
+ tag.stage = 3 /* TYPES */;
633
+ this.pos++;
634
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseAngleBracket;
681
635
  break;
636
+ default:
637
+ tag.hasAttrs = true;
638
+ this.enterState(states_exports.ATTRIBUTE);
639
+ }
640
+ } else {
641
+ this.enterState(states_exports.TAG_NAME);
642
+ }
643
+ }
644
+ },
645
+ return(child, tag) {
646
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
647
+ if (child.state !== states_exports.EXPRESSION)
648
+ return;
649
+ switch (tag.stage) {
650
+ case 1 /* VAR */: {
651
+ if (child.start === child.end) {
652
+ return this.emitError(
653
+ child,
654
+ 23 /* MISSING_TAG_VARIABLE */,
655
+ "A slash was found that was not followed by a variable name or lhs expression"
656
+ );
657
+ }
658
+ (_b = (_a = this.options).onTagVar) == null ? void 0 : _b.call(_a, {
659
+ start: child.start - 1,
660
+ end: child.end,
661
+ value: {
662
+ start: child.start,
663
+ end: child.end
664
+ }
665
+ });
666
+ break;
667
+ }
668
+ case 2 /* ARGUMENT */: {
669
+ const { types } = tag;
670
+ const start = child.start - 1;
671
+ const end = ++this.pos;
672
+ const value = {
673
+ start: child.start,
674
+ end: child.end
675
+ };
676
+ if (this.consumeWhitespaceIfBefore("{")) {
677
+ const attr = this.enterState(states_exports.ATTRIBUTE);
678
+ if (types) {
679
+ attr.start = types.start;
680
+ attr.typeParams = types;
681
+ } else {
682
+ attr.start = start;
683
+ }
684
+ attr.args = { start, end, value };
685
+ this.forward = 0;
686
+ tag.hasAttrs = true;
687
+ } else {
688
+ if (types) {
689
+ (_d = (_c = this.options).onTagTypeArgs) == null ? void 0 : _d.call(_c, types);
690
+ }
691
+ (_f = (_e = this.options).onTagArgs) == null ? void 0 : _f.call(_e, {
692
+ start,
693
+ end,
694
+ value
695
+ });
696
+ }
697
+ break;
698
+ }
699
+ case 3 /* TYPES */: {
700
+ const { types, hasParams, hasArgs } = tag;
701
+ const end = ++this.pos;
702
+ const typeArgs = {
703
+ start: child.start - 1,
704
+ end,
705
+ value: {
706
+ start: child.start,
707
+ end: child.end
682
708
  }
709
+ };
710
+ this.consumeWhitespace();
711
+ const nextCode = this.lookAtCharCodeAhead(0);
712
+ if (!hasParams && nextCode === 124 /* PIPE */) {
713
+ if (types) {
714
+ (_h = (_g = this.options).onTagTypeArgs) == null ? void 0 : _h.call(_g, types);
715
+ }
716
+ (_j = (_i = this.options).onTagTypeParams) == null ? void 0 : _j.call(_i, typeArgs);
717
+ } else if (!hasArgs && nextCode === 40 /* OPEN_PAREN */) {
718
+ if (types) {
719
+ (_l = (_k = this.options).onTagTypeArgs) == null ? void 0 : _l.call(_k, types);
720
+ }
721
+ tag.types = typeArgs;
722
+ } else if (!(types || hasParams || hasArgs)) {
723
+ (_n = (_m = this.options).onTagTypeArgs) == null ? void 0 : _n.call(_m, typeArgs);
724
+ tag.types = typeArgs;
725
+ } else {
726
+ this.emitError(
727
+ child,
728
+ 27 /* INVALID_TAG_TYPES */,
729
+ "Unexpected types. Type arguments must follow a tag name and type paremeters must precede a method or tag parameters."
730
+ );
683
731
  }
684
732
  break;
685
733
  }
734
+ case 4 /* PARAMS */: {
735
+ const end = ++this.pos;
736
+ (_p = (_o = this.options).onTagParams) == null ? void 0 : _p.call(_o, {
737
+ start: child.start - 1,
738
+ end,
739
+ value: {
740
+ start: child.start,
741
+ end: child.end
742
+ }
743
+ });
744
+ break;
745
+ }
686
746
  }
687
747
  }
688
748
  };
@@ -693,6 +753,7 @@ function shouldTerminateConciseTagVar(code, data, pos) {
693
753
  case 124 /* PIPE */:
694
754
  case 40 /* OPEN_PAREN */:
695
755
  case 59 /* SEMICOLON */:
756
+ case 60 /* OPEN_ANGLE_BRACKET */:
696
757
  return true;
697
758
  case 58 /* COLON */:
698
759
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -707,6 +768,7 @@ function shouldTerminateHtmlTagVar(code, data, pos) {
707
768
  case 61 /* EQUAL */:
708
769
  case 40 /* OPEN_PAREN */:
709
770
  case 62 /* CLOSE_ANGLE_BRACKET */:
771
+ case 60 /* OPEN_ANGLE_BRACKET */:
710
772
  return true;
711
773
  case 58 /* COLON */:
712
774
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -730,6 +792,7 @@ var ATTRIBUTE = {
730
792
  stage: 0 /* UNKNOWN */,
731
793
  name: void 0,
732
794
  args: false,
795
+ typeParams: void 0,
733
796
  bound: false,
734
797
  spread: false
735
798
  };
@@ -760,25 +823,37 @@ var ATTRIBUTE = {
760
823
  const expr = this.enterState(states_exports.EXPRESSION);
761
824
  expr.operators = true;
762
825
  expr.terminatedByWhitespace = true;
763
- expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 4 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrValue : shouldTerminateConciseAttrValue : shouldTerminateHtmlAttrValue;
826
+ expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 5 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrValue : shouldTerminateConciseAttrValue : shouldTerminateHtmlAttrValue;
764
827
  } else if (code === 40 /* OPEN_PAREN */) {
765
828
  ensureAttrName(this, attr);
766
829
  attr.stage = 3 /* ARGUMENT */;
767
830
  this.pos++;
768
831
  this.forward = 0;
769
832
  this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen;
833
+ } else if (code === 60 /* OPEN_ANGLE_BRACKET */ && attr.stage === 1 /* NAME */) {
834
+ attr.stage = 4 /* TYPE_PARAMS */;
835
+ this.pos++;
836
+ this.forward = 0;
837
+ this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseAngleBracket;
770
838
  } else if (code === 123 /* OPEN_CURLY_BRACE */ && attr.args) {
771
839
  ensureAttrName(this, attr);
772
- attr.stage = 4 /* BLOCK */;
840
+ attr.stage = 5 /* BLOCK */;
773
841
  this.pos++;
774
842
  this.forward = 0;
775
843
  this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace;
776
844
  } else if (attr.stage === 0 /* UNKNOWN */) {
845
+ if (code === 60 /* OPEN_ANGLE_BRACKET */) {
846
+ return this.emitError(
847
+ this.pos,
848
+ 2 /* INVALID_ATTRIBUTE_NAME */,
849
+ 'Invalid attribute name. Attribute name cannot begin with the "<" character.'
850
+ );
851
+ }
777
852
  attr.stage = 1 /* NAME */;
778
853
  this.forward = 0;
779
854
  const expr = this.enterState(states_exports.EXPRESSION);
780
855
  expr.terminatedByWhitespace = true;
781
- expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 4 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrName : shouldTerminateConciseAttrName : shouldTerminateHtmlAttrName;
856
+ expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 5 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrName : shouldTerminateConciseAttrName : shouldTerminateHtmlAttrName;
782
857
  } else {
783
858
  this.exitState();
784
859
  }
@@ -831,6 +906,12 @@ var ATTRIBUTE = {
831
906
  end,
832
907
  value
833
908
  };
909
+ } else if (attr.typeParams) {
910
+ this.emitError(
911
+ child,
912
+ 1 /* INVALID_ATTRIBUTE_ARGUMENT */,
913
+ "An attribute cannot have both type parameters and arguments"
914
+ );
834
915
  } else {
835
916
  attr.args = true;
836
917
  (_d = (_c = this.options).onAttrArgs) == null ? void 0 : _d.call(_c, {
@@ -841,14 +922,16 @@ var ATTRIBUTE = {
841
922
  }
842
923
  break;
843
924
  }
844
- case 4 /* BLOCK */: {
925
+ case 5 /* BLOCK */: {
845
926
  const params = attr.args;
846
- const start = params.start;
847
927
  const end = ++this.pos;
928
+ const { typeParams } = attr;
929
+ const start = typeParams ? typeParams.start : params.start;
848
930
  (_f = (_e = this.options).onAttrMethod) == null ? void 0 : _f.call(_e, {
849
931
  start,
850
932
  end,
851
933
  params,
934
+ typeParams,
852
935
  body: {
853
936
  start: child.start - 1,
854
937
  end,
@@ -861,6 +944,26 @@ var ATTRIBUTE = {
861
944
  this.exitState();
862
945
  break;
863
946
  }
947
+ case 4 /* TYPE_PARAMS */: {
948
+ const start = child.start - 1;
949
+ const end = ++this.pos;
950
+ if (!this.consumeWhitespaceIfBefore("(")) {
951
+ return this.emitError(
952
+ child,
953
+ 28 /* INVALID_ATTR_TYPE_PARAMS */,
954
+ "Attribute cannot contain type parameters unless it is a shorthand method"
955
+ );
956
+ }
957
+ attr.typeParams = {
958
+ start,
959
+ end,
960
+ value: {
961
+ start: child.start,
962
+ end: child.end
963
+ }
964
+ };
965
+ break;
966
+ }
864
967
  case 2 /* VALUE */: {
865
968
  if (child.start === child.end) {
866
969
  return this.emitError(
@@ -910,6 +1013,7 @@ function shouldTerminateHtmlAttrName(code, data, pos) {
910
1013
  case 61 /* EQUAL */:
911
1014
  case 40 /* OPEN_PAREN */:
912
1015
  case 62 /* CLOSE_ANGLE_BRACKET */:
1016
+ case 60 /* OPEN_ANGLE_BRACKET */:
913
1017
  return true;
914
1018
  case 58 /* COLON */:
915
1019
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -937,6 +1041,7 @@ function shouldTerminateConciseAttrName(code, data, pos) {
937
1041
  case 61 /* EQUAL */:
938
1042
  case 40 /* OPEN_PAREN */:
939
1043
  case 59 /* SEMICOLON */:
1044
+ case 60 /* OPEN_ANGLE_BRACKET */:
940
1045
  return true;
941
1046
  case 58 /* COLON */:
942
1047
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -963,6 +1068,7 @@ function shouldTerminateConciseGroupedAttrName(code, data, pos) {
963
1068
  case 61 /* EQUAL */:
964
1069
  case 40 /* OPEN_PAREN */:
965
1070
  case 93 /* CLOSE_SQUARE_BRACKET */:
1071
+ case 60 /* OPEN_ANGLE_BRACKET */:
966
1072
  return true;
967
1073
  case 58 /* COLON */:
968
1074
  return data.charCodeAt(pos + 1) === 61 /* EQUAL */;
@@ -2186,6 +2292,7 @@ function checkForPlaceholder(parser, code) {
2186
2292
  curCode = parser.lookAtCharCodeAhead(ahead + 2);
2187
2293
  }
2188
2294
  if (curCode === 123 /* OPEN_CURLY_BRACE */) {
2295
+ parser.forward = 0;
2189
2296
  if (ahead) {
2190
2297
  const remainder = ahead % 2;
2191
2298
  const extra = (ahead + remainder) / 2;
@@ -2205,7 +2312,6 @@ function checkForPlaceholder(parser, code) {
2205
2312
  parser.endText();
2206
2313
  parser.enterState(PLACEHOLDER).escape = escape;
2207
2314
  parser.pos += escape ? 2 : 3;
2208
- parser.forward = 0;
2209
2315
  parser.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace;
2210
2316
  return true;
2211
2317
  }
@@ -2304,6 +2410,7 @@ var TAG_NAME = {
2304
2410
  parent,
2305
2411
  start,
2306
2412
  end: start,
2413
+ shorthandCode: -1,
2307
2414
  expressions: [],
2308
2415
  quasis: [{ start, end: start }]
2309
2416
  };
@@ -2383,7 +2490,7 @@ var TAG_NAME = {
2383
2490
  this.pos += 2;
2384
2491
  this.forward = 0;
2385
2492
  this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace;
2386
- } else if (isWhitespaceCode(code) || code === 61 /* EQUAL */ || code === 58 /* COLON */ && this.lookAtCharCodeAhead(1) === 61 /* EQUAL */ || code === 40 /* OPEN_PAREN */ || code === 47 /* FORWARD_SLASH */ || code === 124 /* PIPE */ || (this.isConcise ? code === 59 /* SEMICOLON */ : code === 62 /* CLOSE_ANGLE_BRACKET */)) {
2493
+ } else if (isWhitespaceCode(code) || code === 61 /* EQUAL */ || code === 58 /* COLON */ && this.lookAtCharCodeAhead(1) === 61 /* EQUAL */ || code === 40 /* OPEN_PAREN */ || code === 47 /* FORWARD_SLASH */ || code === 124 /* PIPE */ || code === 60 /* OPEN_ANGLE_BRACKET */ || (this.isConcise ? code === 59 /* SEMICOLON */ : code === 62 /* CLOSE_ANGLE_BRACKET */)) {
2387
2494
  this.activeTag.shorthandEnd = this.pos;
2388
2495
  this.exitState();
2389
2496
  } else if (code === 46 /* PERIOD */ || code === 35 /* NUMBER_SIGN */) {
@@ -4,13 +4,15 @@ declare const enum ATTR_STAGE {
4
4
  NAME = 1,
5
5
  VALUE = 2,
6
6
  ARGUMENT = 3,
7
- BLOCK = 4
7
+ TYPE_PARAMS = 4,
8
+ BLOCK = 5
8
9
  }
9
10
  export interface AttrMeta extends Meta {
10
11
  stage: ATTR_STAGE;
11
12
  name: undefined | Range;
12
13
  valueStart: number;
13
14
  args: boolean | Ranges.AttrMethod["params"];
15
+ typeParams: undefined | Ranges.Value;
14
16
  spread: boolean;
15
17
  bound: boolean;
16
18
  }
@@ -3,8 +3,9 @@ export declare enum TAG_STAGE {
3
3
  UNKNOWN = 0,
4
4
  VAR = 1,
5
5
  ARGUMENT = 2,
6
- PARAMS = 3,
7
- ATTR_GROUP = 4
6
+ TYPES = 3,
7
+ PARAMS = 4,
8
+ ATTR_GROUP = 5
8
9
  }
9
10
  export interface OpenTagMeta extends Meta {
10
11
  type: TagType;
@@ -15,6 +16,8 @@ export interface OpenTagMeta extends Meta {
15
16
  shorthandEnd: number;
16
17
  hasArgs: boolean;
17
18
  hasAttrs: boolean;
19
+ hasParams: boolean;
20
+ types: undefined | Ranges.Value;
18
21
  hasShorthandId: boolean;
19
22
  selfClosed: boolean;
20
23
  indent: string;
@@ -1,5 +1,5 @@
1
1
  import { CODE, StateDefinition, Ranges, Meta } from "../internal";
2
2
  export interface TagNameMeta extends Meta, Ranges.Template {
3
- shorthandCode?: CODE.NUMBER_SIGN | CODE.PERIOD;
3
+ shorthandCode: -1 | CODE.NUMBER_SIGN | CODE.PERIOD;
4
4
  }
5
5
  export declare const TAG_NAME: StateDefinition<TagNameMeta>;
@@ -90,6 +90,7 @@ export declare namespace Ranges {
90
90
  interface AttrMethod extends Range {
91
91
  body: Value;
92
92
  params: Value;
93
+ typeParams: Value | undefined;
93
94
  }
94
95
  interface OpenTagEnd extends Range {
95
96
  selfClosed: boolean;
@@ -121,7 +122,10 @@ export declare enum ErrorCode {
121
122
  MISSING_END_TAG = 22,
122
123
  MISSING_TAG_VARIABLE = 23,
123
124
  RESERVED_TAG_NAME = 24,
124
- ROOT_TAG_ONLY = 25
125
+ ROOT_TAG_ONLY = 25,
126
+ INVALID_TAG_PARAMS = 26,
127
+ INVALID_TAG_TYPES = 27,
128
+ INVALID_ATTR_TYPE_PARAMS = 28
125
129
  }
126
130
  export declare const enum TagType {
127
131
  html = 0,
@@ -142,8 +146,10 @@ export interface ParserOptions {
142
146
  onOpenTagName?(data: Ranges.Template): TagType | void;
143
147
  onTagShorthandId?(data: Ranges.Template): void;
144
148
  onTagShorthandClass?(data: Ranges.Template): void;
149
+ onTagTypeArgs?(data: Ranges.Value): void;
145
150
  onTagVar?(data: Ranges.Value): void;
146
151
  onTagArgs?(data: Ranges.Value): void;
152
+ onTagTypeParams?(data: Ranges.Value): void;
147
153
  onTagParams?(data: Ranges.Value): void;
148
154
  onAttrName?(data: Range): void;
149
155
  onAttrArgs?(data: Ranges.Value): void;
@@ -15,6 +15,7 @@ export declare function getPosition(lines: number[], offset: number): Position;
15
15
  */
16
16
  export declare function getLines(src: string): number[];
17
17
  export declare function htmlEOF(this: Parser): void;
18
+ export declare function matchesCloseAngleBracket(code: number): boolean;
18
19
  export declare function matchesCloseParen(code: number): boolean;
19
20
  export declare function matchesCloseCurlyBrace(code: number): boolean;
20
21
  export declare function matchesPipe(code: number): boolean;
package/package.json CHANGED
@@ -1,31 +1,31 @@
1
1
  {
2
2
  "name": "htmljs-parser",
3
3
  "description": "An HTML parser recognizes content and string placeholders and allows JavaScript expressions as attribute values",
4
- "version": "5.1.5",
4
+ "version": "5.2.1",
5
5
  "devDependencies": {
6
- "@changesets/changelog-github": "^0.4.6",
7
- "@changesets/cli": "^2.24.1",
6
+ "@changesets/changelog-github": "^0.4.7",
7
+ "@changesets/cli": "^2.25.2",
8
8
  "@types/degit": "^2.8.3",
9
- "@types/mocha": "^9.1.1",
10
- "@types/node": "^18.6.4",
11
- "@typescript-eslint/eslint-plugin": "^5.32.0",
12
- "@typescript-eslint/parser": "^5.32.0",
9
+ "@types/mocha": "^10.0.0",
10
+ "@types/node": "^18.11.9",
11
+ "@typescript-eslint/eslint-plugin": "^5.42.0",
12
+ "@typescript-eslint/parser": "^5.42.0",
13
13
  "cross-env": "^7.0.3",
14
14
  "degit": "^2.8.4",
15
- "esbuild": "0.14.53",
16
- "eslint": "^8.21.0",
15
+ "esbuild": "0.15.13",
16
+ "eslint": "^8.26.0",
17
17
  "eslint-config-prettier": "^8.5.0",
18
- "fast-glob": "^3.2.11",
18
+ "fast-glob": "^3.2.12",
19
19
  "fixpack": "^4.0.0",
20
20
  "husky": "^8.0.1",
21
21
  "lint-staged": "^13.0.3",
22
22
  "mitata": "^0.1.6",
23
- "mocha": "^10.0.0",
23
+ "mocha": "^10.1.0",
24
24
  "mocha-snap": "^4.3.0",
25
25
  "nyc": "^15.1.0",
26
26
  "prettier": "^2.7.1",
27
- "tsx": "^3.8.0",
28
- "typescript": "^4.7.4"
27
+ "tsx": "^3.11.0",
28
+ "typescript": "^4.8.4"
29
29
  },
30
30
  "exports": {
31
31
  ".": {