html-validate 6.4.0 → 6.5.0

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/dist/es/core.d.ts CHANGED
@@ -40,28 +40,95 @@ declare class EventHandler {
40
40
  declare enum TokenType {
41
41
  UNICODE_BOM = 1,
42
42
  WHITESPACE = 2,
43
- NEWLINE = 3,
44
- DOCTYPE_OPEN = 4,
45
- DOCTYPE_VALUE = 5,
46
- DOCTYPE_CLOSE = 6,
47
- TAG_OPEN = 7,
48
- TAG_CLOSE = 8,
49
- ATTR_NAME = 9,
50
- ATTR_VALUE = 10,
51
- TEXT = 11,
52
- TEMPLATING = 12,
53
- SCRIPT = 13,
54
- STYLE = 14,
55
- COMMENT = 15,
56
- CONDITIONAL = 16,
57
- DIRECTIVE = 17,
58
- EOF = 18
59
- }
60
- interface Token {
43
+ DOCTYPE_OPEN = 3,
44
+ DOCTYPE_VALUE = 4,
45
+ DOCTYPE_CLOSE = 5,
46
+ TAG_OPEN = 6,
47
+ TAG_CLOSE = 7,
48
+ ATTR_NAME = 8,
49
+ ATTR_VALUE = 9,
50
+ TEXT = 10,
51
+ TEMPLATING = 11,
52
+ SCRIPT = 12,
53
+ STYLE = 13,
54
+ COMMENT = 14,
55
+ CONDITIONAL = 15,
56
+ DIRECTIVE = 16,
57
+ EOF = 17
58
+ }
59
+ interface BaseToken {
61
60
  type: TokenType;
62
61
  location: Location;
63
- data?: any;
64
62
  }
63
+ interface UnicodeBOMToken extends BaseToken {
64
+ type: TokenType.UNICODE_BOM;
65
+ data: [bom: string];
66
+ }
67
+ interface WhitespaceToken extends BaseToken {
68
+ type: TokenType.WHITESPACE;
69
+ data: [text: string];
70
+ }
71
+ interface DoctypeOpenToken extends BaseToken {
72
+ type: TokenType.DOCTYPE_OPEN;
73
+ data: [text: string, tag: string];
74
+ }
75
+ interface DoctypeValueToken extends BaseToken {
76
+ type: TokenType.DOCTYPE_VALUE;
77
+ data: [text: string];
78
+ }
79
+ interface DoctypeCloseToken extends BaseToken {
80
+ type: TokenType.DOCTYPE_CLOSE;
81
+ data: [text: ">"];
82
+ }
83
+ interface TagOpenToken extends BaseToken {
84
+ type: TokenType.TAG_OPEN;
85
+ data: [text: string, close: "/" | "", tag: string];
86
+ }
87
+ interface TagCloseToken extends BaseToken {
88
+ type: TokenType.TAG_CLOSE;
89
+ data: [text: ">" | "/>"];
90
+ }
91
+ interface AttrNameToken extends BaseToken {
92
+ type: TokenType.ATTR_NAME;
93
+ data: [text: string, name: string];
94
+ }
95
+ interface AttrValueToken extends BaseToken {
96
+ type: TokenType.ATTR_VALUE;
97
+ data: [text: string, delimiter: string, value: string, quote?: '"' | "'"];
98
+ }
99
+ interface TextToken extends BaseToken {
100
+ type: TokenType.TEXT;
101
+ data: [text: string];
102
+ }
103
+ interface TemplatingToken extends BaseToken {
104
+ type: TokenType.TEMPLATING;
105
+ data: [text: string];
106
+ }
107
+ interface ScriptToken extends BaseToken {
108
+ type: TokenType.SCRIPT;
109
+ data: [text: string];
110
+ }
111
+ interface StyleToken extends BaseToken {
112
+ type: TokenType.STYLE;
113
+ data: [text: string];
114
+ }
115
+ interface CommentToken extends BaseToken {
116
+ type: TokenType.COMMENT;
117
+ data: [text: string, comment: string];
118
+ }
119
+ interface ConditionalToken extends BaseToken {
120
+ type: TokenType.CONDITIONAL;
121
+ data: [text: string, condition: string];
122
+ }
123
+ interface DirectiveToken extends BaseToken {
124
+ type: TokenType.DIRECTIVE;
125
+ data: [text: string, begin: "[", action: string, rest: string, end: "]" | ""];
126
+ }
127
+ interface EOFToken extends BaseToken {
128
+ type: TokenType.EOF;
129
+ data: [];
130
+ }
131
+ declare type Token = UnicodeBOMToken | WhitespaceToken | DoctypeOpenToken | DoctypeValueToken | DoctypeCloseToken | TagOpenToken | TagCloseToken | AttrNameToken | AttrValueToken | TextToken | TemplatingToken | ScriptToken | StyleToken | CommentToken | ConditionalToken | DirectiveToken | EOFToken;
65
132
 
66
133
  declare type TokenStream = IterableIterator<Token>;
67
134
 
@@ -627,14 +694,14 @@ declare class Parser {
627
694
  * stack when is allowed to omit.
628
695
  */
629
696
  private closeOptional;
630
- protected consumeTag(source: Source, startToken: Token, tokenStream: TokenStream): void;
697
+ protected consumeTag(source: Source, startToken: TagOpenToken, tokenStream: TokenStream): void;
631
698
  protected closeElement(source: Source, node: HtmlElement | null, active: HtmlElement, location: Location): void;
632
699
  private processElement;
633
700
  /**
634
701
  * Discard tokens until the end tag for the foreign element is found.
635
702
  */
636
703
  protected discardForeignBody(source: Source, foreignTagName: string, tokenStream: TokenStream, errorLocation: Location): void;
637
- protected consumeAttribute(source: Source, node: HtmlElement, token: Token, next?: Token): void;
704
+ protected consumeAttribute(source: Source, node: HtmlElement, token: AttrNameToken, next?: Token): void;
638
705
  /**
639
706
  * Takes attribute key token an returns location.
640
707
  */
@@ -652,24 +719,24 @@ declare class Parser {
652
719
  * an aggregate location covering key, quotes if present and value.
653
720
  */
654
721
  private getAttributeLocation;
655
- protected consumeDirective(token: Token): void;
722
+ protected consumeDirective(token: DirectiveToken): void;
656
723
  /**
657
724
  * Consumes conditional comment in tag form.
658
725
  *
659
726
  * See also the related [[consumeCommend]] method.
660
727
  */
661
- protected consumeConditional(token: Token): void;
728
+ protected consumeConditional(token: ConditionalToken): void;
662
729
  /**
663
730
  * Consumes comment token.
664
731
  *
665
732
  * Tries to find IE conditional comments and emits conditional token if
666
733
  * found. See also the related [[consumeConditional]] method.
667
734
  */
668
- protected consumeComment(token: Token): void;
735
+ protected consumeComment(token: CommentToken): void;
669
736
  /**
670
737
  * Consumes doctype tokens. Emits doctype event.
671
738
  */
672
- protected consumeDoctype(startToken: Token, tokenStream: TokenStream): void;
739
+ protected consumeDoctype(startToken: DoctypeOpenToken, tokenStream: TokenStream): void;
673
740
  /**
674
741
  * Return a list of tokens found until the expected token was found.
675
742
  *
@@ -1154,7 +1221,7 @@ declare class HtmlElement extends DOMNode {
1154
1221
  /**
1155
1222
  * @internal
1156
1223
  */
1157
- static fromTokens(startToken: Token, endToken: Token, parent: HtmlElement | null, metaTable: MetaTable | null): HtmlElement;
1224
+ static fromTokens(startToken: TagOpenToken, endToken: TagCloseToken, parent: HtmlElement | null, metaTable: MetaTable | null): HtmlElement;
1158
1225
  /**
1159
1226
  * Returns annotated name if set or defaults to `<tagName>`.
1160
1227
  *
package/dist/es/core.js CHANGED
@@ -1476,8 +1476,8 @@ class DOMTokenList extends Array {
1476
1476
  constructor(value, location) {
1477
1477
  if (value && typeof value === "string") {
1478
1478
  /* replace all whitespace with a single space for easier parsing */
1479
- const condensed = value.replace(/[\t\r\n ]+/g, " ");
1480
- const { tokens, locations } = parse(condensed, location);
1479
+ const normalized = value.replace(/[\t\r\n]/g, " ");
1480
+ const { tokens, locations } = parse(normalized, location);
1481
1481
  super(...tokens);
1482
1482
  this.locations = locations;
1483
1483
  }
@@ -2945,7 +2945,7 @@ var TRANSFORMER_API;
2945
2945
  /** @public */
2946
2946
  const name = "html-validate";
2947
2947
  /** @public */
2948
- const version = "6.4.0";
2948
+ const version = "6.5.0";
2949
2949
  /** @public */
2950
2950
  const homepage = "https://html-validate.org";
2951
2951
  /** @public */
@@ -2984,6 +2984,42 @@ function parseSeverity(value) {
2984
2984
  }
2985
2985
  }
2986
2986
 
2987
+ function escape(value) {
2988
+ return value.replace(/'/g, "\\'");
2989
+ }
2990
+ function format(value, quote = false) {
2991
+ if (value === null) {
2992
+ return "null";
2993
+ }
2994
+ if (typeof value === "number") {
2995
+ return value.toString();
2996
+ }
2997
+ if (typeof value === "string") {
2998
+ return quote ? `'${escape(value)}'` : value;
2999
+ }
3000
+ if (Array.isArray(value)) {
3001
+ const content = value.map((it) => format(it, true)).join(", ");
3002
+ return `[ ${content} ]`;
3003
+ }
3004
+ if (typeof value === "object") {
3005
+ const content = Object.entries(value)
3006
+ .map(([key, nested]) => `${key}: ${format(nested, true)}`)
3007
+ .join(", ");
3008
+ return `{ ${content} }`;
3009
+ }
3010
+ return String(value);
3011
+ }
3012
+ /**
3013
+ * Replaces placeholder `{{ ... }}` with values from given object.
3014
+ *
3015
+ * @internal
3016
+ */
3017
+ function interpolate(text, data) {
3018
+ return text.replace(/{{\s*([^\s]+)\s*}}/g, (match, key) => {
3019
+ return typeof data[key] !== "undefined" ? format(data[key]) : match;
3020
+ });
3021
+ }
3022
+
2987
3023
  const remapEvents = {
2988
3024
  "tag:open": "tag:start",
2989
3025
  "tag:close": "tag:end",
@@ -3118,7 +3154,8 @@ class Rule {
3118
3154
  report(node, message, location, context) {
3119
3155
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3120
3156
  const where = this.findLocation({ node, location, event: this.event });
3121
- this.reporter.add(this, message, this.severity, node, where, context);
3157
+ const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
3158
+ this.reporter.add(this, interpolated, this.severity, node, where, context);
3122
3159
  }
3123
3160
  }
3124
3161
  findLocation(src) {
@@ -3643,22 +3680,21 @@ var TokenType;
3643
3680
  (function (TokenType) {
3644
3681
  TokenType[TokenType["UNICODE_BOM"] = 1] = "UNICODE_BOM";
3645
3682
  TokenType[TokenType["WHITESPACE"] = 2] = "WHITESPACE";
3646
- TokenType[TokenType["NEWLINE"] = 3] = "NEWLINE";
3647
- TokenType[TokenType["DOCTYPE_OPEN"] = 4] = "DOCTYPE_OPEN";
3648
- TokenType[TokenType["DOCTYPE_VALUE"] = 5] = "DOCTYPE_VALUE";
3649
- TokenType[TokenType["DOCTYPE_CLOSE"] = 6] = "DOCTYPE_CLOSE";
3650
- TokenType[TokenType["TAG_OPEN"] = 7] = "TAG_OPEN";
3651
- TokenType[TokenType["TAG_CLOSE"] = 8] = "TAG_CLOSE";
3652
- TokenType[TokenType["ATTR_NAME"] = 9] = "ATTR_NAME";
3653
- TokenType[TokenType["ATTR_VALUE"] = 10] = "ATTR_VALUE";
3654
- TokenType[TokenType["TEXT"] = 11] = "TEXT";
3655
- TokenType[TokenType["TEMPLATING"] = 12] = "TEMPLATING";
3656
- TokenType[TokenType["SCRIPT"] = 13] = "SCRIPT";
3657
- TokenType[TokenType["STYLE"] = 14] = "STYLE";
3658
- TokenType[TokenType["COMMENT"] = 15] = "COMMENT";
3659
- TokenType[TokenType["CONDITIONAL"] = 16] = "CONDITIONAL";
3660
- TokenType[TokenType["DIRECTIVE"] = 17] = "DIRECTIVE";
3661
- TokenType[TokenType["EOF"] = 18] = "EOF";
3683
+ TokenType[TokenType["DOCTYPE_OPEN"] = 3] = "DOCTYPE_OPEN";
3684
+ TokenType[TokenType["DOCTYPE_VALUE"] = 4] = "DOCTYPE_VALUE";
3685
+ TokenType[TokenType["DOCTYPE_CLOSE"] = 5] = "DOCTYPE_CLOSE";
3686
+ TokenType[TokenType["TAG_OPEN"] = 6] = "TAG_OPEN";
3687
+ TokenType[TokenType["TAG_CLOSE"] = 7] = "TAG_CLOSE";
3688
+ TokenType[TokenType["ATTR_NAME"] = 8] = "ATTR_NAME";
3689
+ TokenType[TokenType["ATTR_VALUE"] = 9] = "ATTR_VALUE";
3690
+ TokenType[TokenType["TEXT"] = 10] = "TEXT";
3691
+ TokenType[TokenType["TEMPLATING"] = 11] = "TEMPLATING";
3692
+ TokenType[TokenType["SCRIPT"] = 12] = "SCRIPT";
3693
+ TokenType[TokenType["STYLE"] = 13] = "STYLE";
3694
+ TokenType[TokenType["COMMENT"] = 14] = "COMMENT";
3695
+ TokenType[TokenType["CONDITIONAL"] = 15] = "CONDITIONAL";
3696
+ TokenType[TokenType["DIRECTIVE"] = 16] = "DIRECTIVE";
3697
+ TokenType[TokenType["EOF"] = 17] = "EOF";
3662
3698
  })(TokenType || (TokenType = {}));
3663
3699
 
3664
3700
  /* eslint-disable no-useless-escape */
@@ -3683,7 +3719,7 @@ const MATCH_SCRIPT_DATA = /^[^]*?(?=<\/script)/;
3683
3719
  const MATCH_SCRIPT_END = /^<(\/)(script)/;
3684
3720
  const MATCH_STYLE_DATA = /^[^]*?(?=<\/style)/;
3685
3721
  const MATCH_STYLE_END = /^<(\/)(style)/;
3686
- const MATCH_DIRECTIVE = /^<!--\s*\[html-validate-(.*?)]\s*-->/;
3722
+ const MATCH_DIRECTIVE = /^<!--\s*(\[)html-validate-([a-z0-9-]+)\s*(.*?)(]?)\s*-->/;
3687
3723
  const MATCH_COMMENT = /^<!--([^]*?)-->/;
3688
3724
  const MATCH_CONDITIONAL = /^<!\[([^\]]*?)\]>/;
3689
3725
  class InvalidTokenError extends Error {
@@ -3738,15 +3774,15 @@ class Lexer {
3738
3774
  previousState = context.state;
3739
3775
  previousLength = context.string.length;
3740
3776
  }
3741
- yield this.token(context, TokenType.EOF);
3777
+ yield this.token(context, TokenType.EOF, []);
3742
3778
  }
3743
3779
  token(context, type, data) {
3744
- const size = data ? data[0].length : 0;
3780
+ const size = data.length > 0 ? data[0].length : 0;
3745
3781
  const location = context.getLocation(size);
3746
3782
  return {
3747
3783
  type,
3748
3784
  location,
3749
- data: data ? Array.from(data) : null,
3785
+ data: Array.from(data),
3750
3786
  };
3751
3787
  }
3752
3788
  /* istanbul ignore next: used to provide a better error when an unhandled state happens */
@@ -3771,17 +3807,18 @@ class Lexer {
3771
3807
  }
3772
3808
  }
3773
3809
  *match(context, tests, error) {
3774
- let match = null;
3775
3810
  const n = tests.length;
3776
3811
  for (let i = 0; i < n; i++) {
3777
3812
  const [regex, nextState, tokenType] = tests[i];
3778
- if (regex === false || (match = context.string.match(regex))) {
3813
+ const match = regex ? context.string.match(regex) : [""];
3814
+ if (match) {
3779
3815
  let token = null;
3780
3816
  if (tokenType !== false) {
3781
- yield (token = this.token(context, tokenType, match));
3817
+ token = this.token(context, tokenType, match);
3818
+ yield token;
3782
3819
  }
3783
3820
  const state = this.evalNextState(nextState, token);
3784
- context.consume(match || 0, state);
3821
+ context.consume(match, state);
3785
3822
  this.enter(context, state, match);
3786
3823
  return;
3787
3824
  }
@@ -3828,18 +3865,19 @@ class Lexer {
3828
3865
  *tokenizeTag(context) {
3829
3866
  /* eslint-disable-next-line consistent-return -- exhaustive switch handled by typescript */
3830
3867
  function nextState(token) {
3868
+ const tagCloseToken = token;
3831
3869
  switch (context.contentModel) {
3832
3870
  case ContentModel.TEXT:
3833
3871
  return State.TEXT;
3834
3872
  case ContentModel.SCRIPT:
3835
- if (token && token.data[0][0] !== "/") {
3873
+ if (tagCloseToken && tagCloseToken.data[0][0] !== "/") {
3836
3874
  return State.SCRIPT;
3837
3875
  }
3838
3876
  else {
3839
3877
  return State.TEXT; /* <script/> (not legal but handle it anyway so the lexer doesn't choke on it) */
3840
3878
  }
3841
3879
  case ContentModel.STYLE:
3842
- if (token && token.data[0][0] !== "/") {
3880
+ if (tagCloseToken && tagCloseToken.data[0][0] !== "/") {
3843
3881
  return State.STYLE;
3844
3882
  }
3845
3883
  else {
@@ -10413,6 +10451,9 @@ class ParserError extends Error {
10413
10451
  }
10414
10452
  }
10415
10453
 
10454
+ function isAttrValueToken(token) {
10455
+ return Boolean(token && token.type === TokenType.ATTR_VALUE);
10456
+ }
10416
10457
  /**
10417
10458
  * Parse HTML document into a DOM tree.
10418
10459
  *
@@ -10494,7 +10535,7 @@ class Parser {
10494
10535
  break;
10495
10536
  case TokenType.TEXT:
10496
10537
  case TokenType.TEMPLATING:
10497
- this.appendText(token.data, token.location);
10538
+ this.appendText(token.data[0], token.location);
10498
10539
  break;
10499
10540
  case TokenType.EOF:
10500
10541
  this.closeTree(source, token.location);
@@ -10689,15 +10730,15 @@ class Parser {
10689
10730
  const keyLocation = this.getAttributeKeyLocation(token);
10690
10731
  const valueLocation = this.getAttributeValueLocation(next);
10691
10732
  const location = this.getAttributeLocation(token, next);
10692
- const haveValue = next && next.type === TokenType.ATTR_VALUE;
10733
+ const haveValue = isAttrValueToken(next);
10693
10734
  const attrData = {
10694
10735
  key: token.data[1],
10695
10736
  value: null,
10696
10737
  quote: null,
10697
10738
  };
10698
- if (next && haveValue) {
10739
+ if (haveValue) {
10699
10740
  const [, , value, quote] = next.data;
10700
- attrData.value = value !== null && value !== void 0 ? value : null;
10741
+ attrData.value = value;
10701
10742
  attrData.quote = quote !== null && quote !== void 0 ? quote : null;
10702
10743
  }
10703
10744
  /* get callback to process attributes, default is to just return attribute
@@ -10776,12 +10817,16 @@ class Parser {
10776
10817
  };
10777
10818
  }
10778
10819
  consumeDirective(token) {
10779
- const directive = token.data[1];
10780
- const match = directive.match(/^([a-zA-Z0-9-]+)\s*(.*?)(?:\s*:\s*(.*))?$/);
10820
+ const [text, , action, directive, end] = token.data;
10821
+ if (end === "") {
10822
+ throw new Error(`Missing end bracket "]" on directive "${text}"`);
10823
+ }
10824
+ const match = directive.match(/^(.*?)(?:\s*(?:--|:)\s*(.*))?$/);
10825
+ /* istanbul ignore next: should not be possible, would be emitted as comment token */
10781
10826
  if (!match) {
10782
- throw new Error(`Failed to parse directive "${directive}"`);
10827
+ throw new Error(`Failed to parse directive "${text}"`);
10783
10828
  }
10784
- const [, action, data, comment] = match;
10829
+ const [, data, comment] = match;
10785
10830
  this.trigger("directive", {
10786
10831
  action,
10787
10832
  data,
@@ -10824,7 +10869,8 @@ class Parser {
10824
10869
  */
10825
10870
  consumeDoctype(startToken, tokenStream) {
10826
10871
  const tokens = Array.from(this.consumeUntil(tokenStream, TokenType.DOCTYPE_CLOSE, startToken.location));
10827
- const doctype = tokens[0]; /* first token is the doctype, second is the closing ">" */
10872
+ /* first token is the doctype, second is the closing ">" */
10873
+ const doctype = tokens[0];
10828
10874
  const value = doctype.data[0];
10829
10875
  this.dom.doctype = value;
10830
10876
  this.trigger("doctype", {
@@ -10857,7 +10903,7 @@ class Parser {
10857
10903
  this.trigger("token", {
10858
10904
  location: token.location,
10859
10905
  type: token.type,
10860
- data: token.data ? Array.from(token.data) : undefined,
10906
+ data: Array.from(token.data),
10861
10907
  });
10862
10908
  }
10863
10909
  return it;
@@ -11117,11 +11163,12 @@ class Engine {
11117
11163
  return lines;
11118
11164
  }
11119
11165
  dumpTokens(source) {
11166
+ var _a;
11120
11167
  const lexer = new Lexer();
11121
11168
  const lines = [];
11122
11169
  for (const src of source) {
11123
11170
  for (const token of lexer.tokenize(src)) {
11124
- const data = token.data ? token.data[0] : null;
11171
+ const data = (_a = token.data[0]) !== null && _a !== void 0 ? _a : "";
11125
11172
  lines.push({
11126
11173
  token: TokenType[token.type],
11127
11174
  data,