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.
@@ -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/cjs/core.js CHANGED
@@ -1487,8 +1487,8 @@ class DOMTokenList extends Array {
1487
1487
  constructor(value, location) {
1488
1488
  if (value && typeof value === "string") {
1489
1489
  /* replace all whitespace with a single space for easier parsing */
1490
- const condensed = value.replace(/[\t\r\n ]+/g, " ");
1491
- const { tokens, locations } = parse(condensed, location);
1490
+ const normalized = value.replace(/[\t\r\n]/g, " ");
1491
+ const { tokens, locations } = parse(normalized, location);
1492
1492
  super(...tokens);
1493
1493
  this.locations = locations;
1494
1494
  }
@@ -2956,7 +2956,7 @@ var TRANSFORMER_API;
2956
2956
  /** @public */
2957
2957
  const name = "html-validate";
2958
2958
  /** @public */
2959
- const version = "6.4.0";
2959
+ const version = "6.5.0";
2960
2960
  /** @public */
2961
2961
  const homepage = "https://html-validate.org";
2962
2962
  /** @public */
@@ -2995,6 +2995,42 @@ function parseSeverity(value) {
2995
2995
  }
2996
2996
  }
2997
2997
 
2998
+ function escape(value) {
2999
+ return value.replace(/'/g, "\\'");
3000
+ }
3001
+ function format(value, quote = false) {
3002
+ if (value === null) {
3003
+ return "null";
3004
+ }
3005
+ if (typeof value === "number") {
3006
+ return value.toString();
3007
+ }
3008
+ if (typeof value === "string") {
3009
+ return quote ? `'${escape(value)}'` : value;
3010
+ }
3011
+ if (Array.isArray(value)) {
3012
+ const content = value.map((it) => format(it, true)).join(", ");
3013
+ return `[ ${content} ]`;
3014
+ }
3015
+ if (typeof value === "object") {
3016
+ const content = Object.entries(value)
3017
+ .map(([key, nested]) => `${key}: ${format(nested, true)}`)
3018
+ .join(", ");
3019
+ return `{ ${content} }`;
3020
+ }
3021
+ return String(value);
3022
+ }
3023
+ /**
3024
+ * Replaces placeholder `{{ ... }}` with values from given object.
3025
+ *
3026
+ * @internal
3027
+ */
3028
+ function interpolate(text, data) {
3029
+ return text.replace(/{{\s*([^\s]+)\s*}}/g, (match, key) => {
3030
+ return typeof data[key] !== "undefined" ? format(data[key]) : match;
3031
+ });
3032
+ }
3033
+
2998
3034
  const remapEvents = {
2999
3035
  "tag:open": "tag:start",
3000
3036
  "tag:close": "tag:end",
@@ -3129,7 +3165,8 @@ class Rule {
3129
3165
  report(node, message, location, context) {
3130
3166
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3131
3167
  const where = this.findLocation({ node, location, event: this.event });
3132
- this.reporter.add(this, message, this.severity, node, where, context);
3168
+ const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
3169
+ this.reporter.add(this, interpolated, this.severity, node, where, context);
3133
3170
  }
3134
3171
  }
3135
3172
  findLocation(src) {
@@ -3654,22 +3691,21 @@ exports.TokenType = void 0;
3654
3691
  (function (TokenType) {
3655
3692
  TokenType[TokenType["UNICODE_BOM"] = 1] = "UNICODE_BOM";
3656
3693
  TokenType[TokenType["WHITESPACE"] = 2] = "WHITESPACE";
3657
- TokenType[TokenType["NEWLINE"] = 3] = "NEWLINE";
3658
- TokenType[TokenType["DOCTYPE_OPEN"] = 4] = "DOCTYPE_OPEN";
3659
- TokenType[TokenType["DOCTYPE_VALUE"] = 5] = "DOCTYPE_VALUE";
3660
- TokenType[TokenType["DOCTYPE_CLOSE"] = 6] = "DOCTYPE_CLOSE";
3661
- TokenType[TokenType["TAG_OPEN"] = 7] = "TAG_OPEN";
3662
- TokenType[TokenType["TAG_CLOSE"] = 8] = "TAG_CLOSE";
3663
- TokenType[TokenType["ATTR_NAME"] = 9] = "ATTR_NAME";
3664
- TokenType[TokenType["ATTR_VALUE"] = 10] = "ATTR_VALUE";
3665
- TokenType[TokenType["TEXT"] = 11] = "TEXT";
3666
- TokenType[TokenType["TEMPLATING"] = 12] = "TEMPLATING";
3667
- TokenType[TokenType["SCRIPT"] = 13] = "SCRIPT";
3668
- TokenType[TokenType["STYLE"] = 14] = "STYLE";
3669
- TokenType[TokenType["COMMENT"] = 15] = "COMMENT";
3670
- TokenType[TokenType["CONDITIONAL"] = 16] = "CONDITIONAL";
3671
- TokenType[TokenType["DIRECTIVE"] = 17] = "DIRECTIVE";
3672
- TokenType[TokenType["EOF"] = 18] = "EOF";
3694
+ TokenType[TokenType["DOCTYPE_OPEN"] = 3] = "DOCTYPE_OPEN";
3695
+ TokenType[TokenType["DOCTYPE_VALUE"] = 4] = "DOCTYPE_VALUE";
3696
+ TokenType[TokenType["DOCTYPE_CLOSE"] = 5] = "DOCTYPE_CLOSE";
3697
+ TokenType[TokenType["TAG_OPEN"] = 6] = "TAG_OPEN";
3698
+ TokenType[TokenType["TAG_CLOSE"] = 7] = "TAG_CLOSE";
3699
+ TokenType[TokenType["ATTR_NAME"] = 8] = "ATTR_NAME";
3700
+ TokenType[TokenType["ATTR_VALUE"] = 9] = "ATTR_VALUE";
3701
+ TokenType[TokenType["TEXT"] = 10] = "TEXT";
3702
+ TokenType[TokenType["TEMPLATING"] = 11] = "TEMPLATING";
3703
+ TokenType[TokenType["SCRIPT"] = 12] = "SCRIPT";
3704
+ TokenType[TokenType["STYLE"] = 13] = "STYLE";
3705
+ TokenType[TokenType["COMMENT"] = 14] = "COMMENT";
3706
+ TokenType[TokenType["CONDITIONAL"] = 15] = "CONDITIONAL";
3707
+ TokenType[TokenType["DIRECTIVE"] = 16] = "DIRECTIVE";
3708
+ TokenType[TokenType["EOF"] = 17] = "EOF";
3673
3709
  })(exports.TokenType || (exports.TokenType = {}));
3674
3710
 
3675
3711
  /* eslint-disable no-useless-escape */
@@ -3694,7 +3730,7 @@ const MATCH_SCRIPT_DATA = /^[^]*?(?=<\/script)/;
3694
3730
  const MATCH_SCRIPT_END = /^<(\/)(script)/;
3695
3731
  const MATCH_STYLE_DATA = /^[^]*?(?=<\/style)/;
3696
3732
  const MATCH_STYLE_END = /^<(\/)(style)/;
3697
- const MATCH_DIRECTIVE = /^<!--\s*\[html-validate-(.*?)]\s*-->/;
3733
+ const MATCH_DIRECTIVE = /^<!--\s*(\[)html-validate-([a-z0-9-]+)\s*(.*?)(]?)\s*-->/;
3698
3734
  const MATCH_COMMENT = /^<!--([^]*?)-->/;
3699
3735
  const MATCH_CONDITIONAL = /^<!\[([^\]]*?)\]>/;
3700
3736
  class InvalidTokenError extends Error {
@@ -3749,15 +3785,15 @@ class Lexer {
3749
3785
  previousState = context.state;
3750
3786
  previousLength = context.string.length;
3751
3787
  }
3752
- yield this.token(context, exports.TokenType.EOF);
3788
+ yield this.token(context, exports.TokenType.EOF, []);
3753
3789
  }
3754
3790
  token(context, type, data) {
3755
- const size = data ? data[0].length : 0;
3791
+ const size = data.length > 0 ? data[0].length : 0;
3756
3792
  const location = context.getLocation(size);
3757
3793
  return {
3758
3794
  type,
3759
3795
  location,
3760
- data: data ? Array.from(data) : null,
3796
+ data: Array.from(data),
3761
3797
  };
3762
3798
  }
3763
3799
  /* istanbul ignore next: used to provide a better error when an unhandled state happens */
@@ -3782,17 +3818,18 @@ class Lexer {
3782
3818
  }
3783
3819
  }
3784
3820
  *match(context, tests, error) {
3785
- let match = null;
3786
3821
  const n = tests.length;
3787
3822
  for (let i = 0; i < n; i++) {
3788
3823
  const [regex, nextState, tokenType] = tests[i];
3789
- if (regex === false || (match = context.string.match(regex))) {
3824
+ const match = regex ? context.string.match(regex) : [""];
3825
+ if (match) {
3790
3826
  let token = null;
3791
3827
  if (tokenType !== false) {
3792
- yield (token = this.token(context, tokenType, match));
3828
+ token = this.token(context, tokenType, match);
3829
+ yield token;
3793
3830
  }
3794
3831
  const state = this.evalNextState(nextState, token);
3795
- context.consume(match || 0, state);
3832
+ context.consume(match, state);
3796
3833
  this.enter(context, state, match);
3797
3834
  return;
3798
3835
  }
@@ -3839,18 +3876,19 @@ class Lexer {
3839
3876
  *tokenizeTag(context) {
3840
3877
  /* eslint-disable-next-line consistent-return -- exhaustive switch handled by typescript */
3841
3878
  function nextState(token) {
3879
+ const tagCloseToken = token;
3842
3880
  switch (context.contentModel) {
3843
3881
  case ContentModel.TEXT:
3844
3882
  return State.TEXT;
3845
3883
  case ContentModel.SCRIPT:
3846
- if (token && token.data[0][0] !== "/") {
3884
+ if (tagCloseToken && tagCloseToken.data[0][0] !== "/") {
3847
3885
  return State.SCRIPT;
3848
3886
  }
3849
3887
  else {
3850
3888
  return State.TEXT; /* <script/> (not legal but handle it anyway so the lexer doesn't choke on it) */
3851
3889
  }
3852
3890
  case ContentModel.STYLE:
3853
- if (token && token.data[0][0] !== "/") {
3891
+ if (tagCloseToken && tagCloseToken.data[0][0] !== "/") {
3854
3892
  return State.STYLE;
3855
3893
  }
3856
3894
  else {
@@ -10424,6 +10462,9 @@ class ParserError extends Error {
10424
10462
  }
10425
10463
  }
10426
10464
 
10465
+ function isAttrValueToken(token) {
10466
+ return Boolean(token && token.type === exports.TokenType.ATTR_VALUE);
10467
+ }
10427
10468
  /**
10428
10469
  * Parse HTML document into a DOM tree.
10429
10470
  *
@@ -10505,7 +10546,7 @@ class Parser {
10505
10546
  break;
10506
10547
  case exports.TokenType.TEXT:
10507
10548
  case exports.TokenType.TEMPLATING:
10508
- this.appendText(token.data, token.location);
10549
+ this.appendText(token.data[0], token.location);
10509
10550
  break;
10510
10551
  case exports.TokenType.EOF:
10511
10552
  this.closeTree(source, token.location);
@@ -10700,15 +10741,15 @@ class Parser {
10700
10741
  const keyLocation = this.getAttributeKeyLocation(token);
10701
10742
  const valueLocation = this.getAttributeValueLocation(next);
10702
10743
  const location = this.getAttributeLocation(token, next);
10703
- const haveValue = next && next.type === exports.TokenType.ATTR_VALUE;
10744
+ const haveValue = isAttrValueToken(next);
10704
10745
  const attrData = {
10705
10746
  key: token.data[1],
10706
10747
  value: null,
10707
10748
  quote: null,
10708
10749
  };
10709
- if (next && haveValue) {
10750
+ if (haveValue) {
10710
10751
  const [, , value, quote] = next.data;
10711
- attrData.value = value !== null && value !== void 0 ? value : null;
10752
+ attrData.value = value;
10712
10753
  attrData.quote = quote !== null && quote !== void 0 ? quote : null;
10713
10754
  }
10714
10755
  /* get callback to process attributes, default is to just return attribute
@@ -10787,12 +10828,16 @@ class Parser {
10787
10828
  };
10788
10829
  }
10789
10830
  consumeDirective(token) {
10790
- const directive = token.data[1];
10791
- const match = directive.match(/^([a-zA-Z0-9-]+)\s*(.*?)(?:\s*:\s*(.*))?$/);
10831
+ const [text, , action, directive, end] = token.data;
10832
+ if (end === "") {
10833
+ throw new Error(`Missing end bracket "]" on directive "${text}"`);
10834
+ }
10835
+ const match = directive.match(/^(.*?)(?:\s*(?:--|:)\s*(.*))?$/);
10836
+ /* istanbul ignore next: should not be possible, would be emitted as comment token */
10792
10837
  if (!match) {
10793
- throw new Error(`Failed to parse directive "${directive}"`);
10838
+ throw new Error(`Failed to parse directive "${text}"`);
10794
10839
  }
10795
- const [, action, data, comment] = match;
10840
+ const [, data, comment] = match;
10796
10841
  this.trigger("directive", {
10797
10842
  action,
10798
10843
  data,
@@ -10835,7 +10880,8 @@ class Parser {
10835
10880
  */
10836
10881
  consumeDoctype(startToken, tokenStream) {
10837
10882
  const tokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.DOCTYPE_CLOSE, startToken.location));
10838
- const doctype = tokens[0]; /* first token is the doctype, second is the closing ">" */
10883
+ /* first token is the doctype, second is the closing ">" */
10884
+ const doctype = tokens[0];
10839
10885
  const value = doctype.data[0];
10840
10886
  this.dom.doctype = value;
10841
10887
  this.trigger("doctype", {
@@ -10868,7 +10914,7 @@ class Parser {
10868
10914
  this.trigger("token", {
10869
10915
  location: token.location,
10870
10916
  type: token.type,
10871
- data: token.data ? Array.from(token.data) : undefined,
10917
+ data: Array.from(token.data),
10872
10918
  });
10873
10919
  }
10874
10920
  return it;
@@ -11128,11 +11174,12 @@ class Engine {
11128
11174
  return lines;
11129
11175
  }
11130
11176
  dumpTokens(source) {
11177
+ var _a;
11131
11178
  const lexer = new Lexer();
11132
11179
  const lines = [];
11133
11180
  for (const src of source) {
11134
11181
  for (const token of lexer.tokenize(src)) {
11135
- const data = token.data ? token.data[0] : null;
11182
+ const data = (_a = token.data[0]) !== null && _a !== void 0 ? _a : "";
11136
11183
  lines.push({
11137
11184
  token: exports.TokenType[token.type],
11138
11185
  data,