html-validate 6.6.1 → 6.8.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.
Files changed (43) hide show
  1. package/dist/cjs/browser.d.ts +1 -1
  2. package/dist/cjs/browser.js +2 -0
  3. package/dist/cjs/browser.js.map +1 -1
  4. package/dist/cjs/cli.js +9 -5
  5. package/dist/cjs/cli.js.map +1 -1
  6. package/dist/cjs/core.d.ts +15 -8
  7. package/dist/cjs/core.js +399 -159
  8. package/dist/cjs/core.js.map +1 -1
  9. package/dist/cjs/html-validate.js +11 -6
  10. package/dist/cjs/html-validate.js.map +1 -1
  11. package/dist/cjs/index.d.ts +2 -2
  12. package/dist/cjs/index.js +2 -0
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/jest-lib.js +83 -65
  15. package/dist/cjs/jest-lib.js.map +1 -1
  16. package/dist/cjs/jest.d.ts +35 -2
  17. package/dist/cjs/jest.js +14 -3
  18. package/dist/cjs/jest.js.map +1 -1
  19. package/dist/cjs/test-utils.d.ts +1 -1
  20. package/dist/es/browser.d.ts +1 -1
  21. package/dist/es/browser.js +2 -0
  22. package/dist/es/browser.js.map +1 -1
  23. package/dist/es/cli.js +9 -5
  24. package/dist/es/cli.js.map +1 -1
  25. package/dist/es/core.d.ts +15 -8
  26. package/dist/es/core.js +322 -103
  27. package/dist/es/core.js.map +1 -1
  28. package/dist/es/html-validate.js +11 -6
  29. package/dist/es/html-validate.js.map +1 -1
  30. package/dist/es/index.d.ts +2 -2
  31. package/dist/es/index.js +2 -0
  32. package/dist/es/index.js.map +1 -1
  33. package/dist/es/jest-lib.js +76 -64
  34. package/dist/es/jest-lib.js.map +1 -1
  35. package/dist/es/jest.d.ts +35 -2
  36. package/dist/es/jest.js +15 -3
  37. package/dist/es/jest.js.map +1 -1
  38. package/dist/es/test-utils.d.ts +1 -1
  39. package/elements/html5.js +1881 -0
  40. package/package.json +38 -29
  41. package/dist/cjs/jest-lib.d.ts +0 -35
  42. package/dist/es/jest-lib.d.ts +0 -35
  43. package/elements/html5.json +0 -1876
package/dist/cjs/core.js CHANGED
@@ -4,6 +4,8 @@ var fs = require('fs');
4
4
  var betterAjvErrors = require('@sidvind/better-ajv-errors');
5
5
  var Ajv = require('ajv');
6
6
  var deepmerge = require('deepmerge');
7
+ var espree = require('espree');
8
+ var walk = require('acorn-walk');
7
9
  var path = require('path');
8
10
  var semver = require('semver');
9
11
  var kleur = require('kleur');
@@ -12,10 +14,30 @@ var stylishImpl = require('@html-validate/stylish');
12
14
 
13
15
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
14
16
 
17
+ function _interopNamespace(e) {
18
+ if (e && e.__esModule) return e;
19
+ var n = Object.create(null);
20
+ if (e) {
21
+ Object.keys(e).forEach(function (k) {
22
+ if (k !== 'default') {
23
+ var d = Object.getOwnPropertyDescriptor(e, k);
24
+ Object.defineProperty(n, k, d.get ? d : {
25
+ enumerable: true,
26
+ get: function () { return e[k]; }
27
+ });
28
+ }
29
+ });
30
+ }
31
+ n["default"] = e;
32
+ return Object.freeze(n);
33
+ }
34
+
15
35
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
16
36
  var betterAjvErrors__default = /*#__PURE__*/_interopDefaultLegacy(betterAjvErrors);
17
37
  var Ajv__default = /*#__PURE__*/_interopDefaultLegacy(Ajv);
18
38
  var deepmerge__default = /*#__PURE__*/_interopDefaultLegacy(deepmerge);
39
+ var espree__namespace = /*#__PURE__*/_interopNamespace(espree);
40
+ var walk__namespace = /*#__PURE__*/_interopNamespace(walk);
19
41
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
20
42
  var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);
21
43
  var kleur__default = /*#__PURE__*/_interopDefaultLegacy(kleur);
@@ -256,7 +278,7 @@ class NestedError extends Error {
256
278
  constructor(message, nested) {
257
279
  super(message);
258
280
  Error.captureStackTrace(this, NestedError);
259
- if (nested) {
281
+ if (nested && nested.stack) {
260
282
  this.stack += `\nCaused by: ${nested.stack}`;
261
283
  }
262
284
  }
@@ -1211,6 +1233,27 @@ class Attribute {
1211
1233
  }
1212
1234
  }
1213
1235
 
1236
+ function getCSSDeclarations(value) {
1237
+ return value
1238
+ .trim()
1239
+ .split(";")
1240
+ .filter(Boolean)
1241
+ .map((it) => {
1242
+ const [property, value] = it.split(":", 2);
1243
+ return [property.trim(), value ? value.trim() : ""];
1244
+ });
1245
+ }
1246
+ /**
1247
+ * @internal
1248
+ */
1249
+ function parseCssDeclaration(value) {
1250
+ if (!value || value instanceof DynamicValue) {
1251
+ return {};
1252
+ }
1253
+ const pairs = getCSSDeclarations(value);
1254
+ return Object.fromEntries(pairs);
1255
+ }
1256
+
1214
1257
  function sliceSize(size, begin, end) {
1215
1258
  if (typeof size !== "number") {
1216
1259
  return size;
@@ -1849,8 +1892,13 @@ exports.NodeClosed = void 0;
1849
1892
  NodeClosed[NodeClosed["VoidSelfClosed"] = 3] = "VoidSelfClosed";
1850
1893
  NodeClosed[NodeClosed["ImplicitClosed"] = 4] = "ImplicitClosed";
1851
1894
  })(exports.NodeClosed || (exports.NodeClosed = {}));
1852
- function isElement(node) {
1853
- return node.nodeType === NodeType.ELEMENT_NODE;
1895
+ /**
1896
+ * Returns true if the node is an element node.
1897
+ *
1898
+ * @public
1899
+ */
1900
+ function isElementNode(node) {
1901
+ return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
1854
1902
  }
1855
1903
  function isValidTagName(tagName) {
1856
1904
  return Boolean(tagName !== "" && tagName !== "*");
@@ -1924,7 +1972,7 @@ class HtmlElement extends DOMNode {
1924
1972
  * Similar to childNodes but only elements.
1925
1973
  */
1926
1974
  get childElements() {
1927
- return this.childNodes.filter(isElement);
1975
+ return this.childNodes.filter(isElementNode);
1928
1976
  }
1929
1977
  /**
1930
1978
  * Find the first ancestor matching a selector.
@@ -2154,6 +2202,10 @@ class HtmlElement extends DOMNode {
2154
2202
  get id() {
2155
2203
  return this.getAttributeValue("id");
2156
2204
  }
2205
+ get style() {
2206
+ const attr = this.getAttribute("style");
2207
+ return parseCssDeclaration(attr === null || attr === void 0 ? void 0 : attr.value);
2208
+ }
2157
2209
  /**
2158
2210
  * Returns the first child element or null if there are no child elements.
2159
2211
  */
@@ -2185,8 +2237,9 @@ class HtmlElement extends DOMNode {
2185
2237
  }, []);
2186
2238
  }
2187
2239
  querySelector(selector) {
2240
+ var _a;
2188
2241
  const it = this.querySelectorImpl(selector);
2189
- return it.next().value || null;
2242
+ return (_a = it.next().value) !== null && _a !== void 0 ? _a : null; // eslint-disable-line @typescript-eslint/no-unsafe-return
2190
2243
  }
2191
2244
  querySelectorAll(selector) {
2192
2245
  const it = this.querySelectorImpl(selector);
@@ -2753,8 +2806,6 @@ var configurationSchema = {
2753
2806
  };
2754
2807
 
2755
2808
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2756
- const espree = legacyRequire("espree");
2757
- const walk = legacyRequire("acorn-walk");
2758
2809
  function joinTemplateLiteral(nodes) {
2759
2810
  let offset = nodes[0].start + 1;
2760
2811
  let output = "";
@@ -2870,7 +2921,7 @@ class TemplateExtractor {
2870
2921
  }
2871
2922
  static fromFilename(filename) {
2872
2923
  const source = fs__default["default"].readFileSync(filename, "utf-8");
2873
- const ast = espree.parse(source, {
2924
+ const ast = espree__namespace.parse(source, {
2874
2925
  ecmaVersion: 2017,
2875
2926
  sourceType: "module",
2876
2927
  loc: true,
@@ -2889,7 +2940,7 @@ class TemplateExtractor {
2889
2940
  * `Source`. Defauls to `"inline"`.
2890
2941
  */
2891
2942
  static fromString(source, filename) {
2892
- const ast = espree.parse(source, {
2943
+ const ast = espree__namespace.parse(source, {
2893
2944
  ecmaVersion: 2017,
2894
2945
  sourceType: "module",
2895
2946
  loc: true,
@@ -2935,7 +2986,8 @@ class TemplateExtractor {
2935
2986
  extractObjectProperty(key) {
2936
2987
  const result = [];
2937
2988
  const { filename, data } = this;
2938
- walk.simple(this.ast, {
2989
+ const node = this.ast;
2990
+ walk__namespace.simple(node, {
2939
2991
  Property(node) {
2940
2992
  if (compareKey(node.key, key, filename)) {
2941
2993
  const source = extractLiteral(node.value, filename, data);
@@ -2959,7 +3011,7 @@ var TRANSFORMER_API;
2959
3011
  /** @public */
2960
3012
  const name = "html-validate";
2961
3013
  /** @public */
2962
- const version = "6.6.1";
3014
+ const version = "6.8.0";
2963
3015
  /** @public */
2964
3016
  const homepage = "https://html-validate.org";
2965
3017
  /** @public */
@@ -3280,7 +3332,7 @@ function ruleDocumentationUrl(filename) {
3280
3332
  return `${homepage}/rules/${normalized}.html`;
3281
3333
  }
3282
3334
 
3283
- const defaults$p = {
3335
+ const defaults$q = {
3284
3336
  allowExternal: true,
3285
3337
  allowRelative: true,
3286
3338
  allowAbsolute: true,
@@ -3324,7 +3376,7 @@ function matchList(value, list) {
3324
3376
  }
3325
3377
  class AllowedLinks extends Rule {
3326
3378
  constructor(options) {
3327
- super({ ...defaults$p, ...options });
3379
+ super({ ...defaults$q, ...options });
3328
3380
  this.allowExternal = parseAllow(this.options.allowExternal);
3329
3381
  this.allowRelative = parseAllow(this.options.allowRelative);
3330
3382
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3603,7 +3655,7 @@ class CaseStyle {
3603
3655
  default: {
3604
3656
  const last = names.slice(-1);
3605
3657
  const rest = names.slice(0, -1);
3606
- return `${rest.join(", ")} or ${last}`;
3658
+ return `${rest.join(", ")} or ${last[0]}`;
3607
3659
  }
3608
3660
  }
3609
3661
  }
@@ -3619,19 +3671,19 @@ class CaseStyle {
3619
3671
  case "camelcase":
3620
3672
  return { pattern: /^[a-z][A-Za-z]*$/, name: "camelCase" };
3621
3673
  default:
3622
- throw new ConfigError(`Invalid style "${style}" for ${ruleId} rule`);
3674
+ throw new ConfigError(`Invalid style "${cur}" for ${ruleId} rule`);
3623
3675
  }
3624
3676
  });
3625
3677
  }
3626
3678
  }
3627
3679
 
3628
- const defaults$o = {
3680
+ const defaults$p = {
3629
3681
  style: "lowercase",
3630
3682
  ignoreForeign: true,
3631
3683
  };
3632
3684
  class AttrCase extends Rule {
3633
3685
  constructor(options) {
3634
- super({ ...defaults$o, ...options });
3686
+ super({ ...defaults$p, ...options });
3635
3687
  this.style = new CaseStyle(this.options.style, "attr-case");
3636
3688
  }
3637
3689
  static schema() {
@@ -3658,8 +3710,11 @@ class AttrCase extends Rule {
3658
3710
  };
3659
3711
  }
3660
3712
  documentation() {
3713
+ const { style } = this.options;
3661
3714
  return {
3662
- description: `Attribute name must be ${this.options.style}.`,
3715
+ description: Array.isArray(style)
3716
+ ? [`Attribute name must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
3717
+ : `Attribute name must be in ${style}.`,
3663
3718
  url: ruleDocumentationUrl("@/rules/attr-case.ts"),
3664
3719
  };
3665
3720
  }
@@ -3690,7 +3745,7 @@ class AttrCase extends Rule {
3690
3745
  }
3691
3746
  }
3692
3747
 
3693
- exports.TokenType = void 0;
3748
+ var TokenType;
3694
3749
  (function (TokenType) {
3695
3750
  TokenType[TokenType["UNICODE_BOM"] = 1] = "UNICODE_BOM";
3696
3751
  TokenType[TokenType["WHITESPACE"] = 2] = "WHITESPACE";
@@ -3709,7 +3764,7 @@ exports.TokenType = void 0;
3709
3764
  TokenType[TokenType["CONDITIONAL"] = 15] = "CONDITIONAL";
3710
3765
  TokenType[TokenType["DIRECTIVE"] = 16] = "DIRECTIVE";
3711
3766
  TokenType[TokenType["EOF"] = 17] = "EOF";
3712
- })(exports.TokenType || (exports.TokenType = {}));
3767
+ })(TokenType || (TokenType = {}));
3713
3768
 
3714
3769
  /* eslint-disable no-useless-escape */
3715
3770
  const MATCH_UNICODE_BOM = /^\uFEFF/;
@@ -3788,7 +3843,7 @@ class Lexer {
3788
3843
  previousState = context.state;
3789
3844
  previousLength = context.string.length;
3790
3845
  }
3791
- yield this.token(context, exports.TokenType.EOF, []);
3846
+ yield this.token(context, TokenType.EOF, []);
3792
3847
  }
3793
3848
  token(context, type, data) {
3794
3849
  const size = data.length > 0 ? data[0].length : 0;
@@ -3859,21 +3914,21 @@ class Lexer {
3859
3914
  }
3860
3915
  *tokenizeInitial(context) {
3861
3916
  yield* this.match(context, [
3862
- [MATCH_UNICODE_BOM, State.INITIAL, exports.TokenType.UNICODE_BOM],
3917
+ [MATCH_UNICODE_BOM, State.INITIAL, TokenType.UNICODE_BOM],
3863
3918
  [MATCH_XML_TAG, State.INITIAL, false],
3864
- [MATCH_DOCTYPE_OPEN, State.DOCTYPE, exports.TokenType.DOCTYPE_OPEN],
3865
- [MATCH_WHITESPACE, State.INITIAL, exports.TokenType.WHITESPACE],
3866
- [MATCH_DIRECTIVE, State.INITIAL, exports.TokenType.DIRECTIVE],
3867
- [MATCH_CONDITIONAL, State.INITIAL, exports.TokenType.CONDITIONAL],
3868
- [MATCH_COMMENT, State.INITIAL, exports.TokenType.COMMENT],
3919
+ [MATCH_DOCTYPE_OPEN, State.DOCTYPE, TokenType.DOCTYPE_OPEN],
3920
+ [MATCH_WHITESPACE, State.INITIAL, TokenType.WHITESPACE],
3921
+ [MATCH_DIRECTIVE, State.INITIAL, TokenType.DIRECTIVE],
3922
+ [MATCH_CONDITIONAL, State.INITIAL, TokenType.CONDITIONAL],
3923
+ [MATCH_COMMENT, State.INITIAL, TokenType.COMMENT],
3869
3924
  [false, State.TEXT, false],
3870
3925
  ], "expected doctype");
3871
3926
  }
3872
3927
  *tokenizeDoctype(context) {
3873
3928
  yield* this.match(context, [
3874
- [MATCH_WHITESPACE, State.DOCTYPE, exports.TokenType.WHITESPACE],
3875
- [MATCH_DOCTYPE_VALUE, State.DOCTYPE, exports.TokenType.DOCTYPE_VALUE],
3876
- [MATCH_DOCTYPE_CLOSE, State.TEXT, exports.TokenType.DOCTYPE_CLOSE],
3929
+ [MATCH_WHITESPACE, State.DOCTYPE, TokenType.WHITESPACE],
3930
+ [MATCH_DOCTYPE_VALUE, State.DOCTYPE, TokenType.DOCTYPE_VALUE],
3931
+ [MATCH_DOCTYPE_CLOSE, State.TEXT, TokenType.DOCTYPE_CLOSE],
3877
3932
  ], "expected doctype name");
3878
3933
  }
3879
3934
  *tokenizeTag(context) {
@@ -3900,30 +3955,30 @@ class Lexer {
3900
3955
  }
3901
3956
  }
3902
3957
  yield* this.match(context, [
3903
- [MATCH_TAG_CLOSE, nextState, exports.TokenType.TAG_CLOSE],
3904
- [MATCH_ATTR_START, State.ATTR, exports.TokenType.ATTR_NAME],
3905
- [MATCH_WHITESPACE, State.TAG, exports.TokenType.WHITESPACE],
3958
+ [MATCH_TAG_CLOSE, nextState, TokenType.TAG_CLOSE],
3959
+ [MATCH_ATTR_START, State.ATTR, TokenType.ATTR_NAME],
3960
+ [MATCH_WHITESPACE, State.TAG, TokenType.WHITESPACE],
3906
3961
  ], 'expected attribute, ">" or "/>"');
3907
3962
  }
3908
3963
  *tokenizeAttr(context) {
3909
3964
  yield* this.match(context, [
3910
- [MATCH_ATTR_SINGLE, State.TAG, exports.TokenType.ATTR_VALUE],
3911
- [MATCH_ATTR_DOUBLE, State.TAG, exports.TokenType.ATTR_VALUE],
3912
- [MATCH_ATTR_UNQUOTED, State.TAG, exports.TokenType.ATTR_VALUE],
3965
+ [MATCH_ATTR_SINGLE, State.TAG, TokenType.ATTR_VALUE],
3966
+ [MATCH_ATTR_DOUBLE, State.TAG, TokenType.ATTR_VALUE],
3967
+ [MATCH_ATTR_UNQUOTED, State.TAG, TokenType.ATTR_VALUE],
3913
3968
  [false, State.TAG, false],
3914
3969
  ], 'expected attribute, ">" or "/>"');
3915
3970
  }
3916
3971
  *tokenizeText(context) {
3917
3972
  yield* this.match(context, [
3918
- [MATCH_WHITESPACE, State.TEXT, exports.TokenType.WHITESPACE],
3973
+ [MATCH_WHITESPACE, State.TEXT, TokenType.WHITESPACE],
3919
3974
  [MATCH_CDATA_BEGIN, State.CDATA, false],
3920
- [MATCH_DIRECTIVE, State.TEXT, exports.TokenType.DIRECTIVE],
3921
- [MATCH_CONDITIONAL, State.TEXT, exports.TokenType.CONDITIONAL],
3922
- [MATCH_COMMENT, State.TEXT, exports.TokenType.COMMENT],
3923
- [MATCH_TEMPLATING, State.TEXT, exports.TokenType.TEMPLATING],
3924
- [MATCH_TAG_OPEN, State.TAG, exports.TokenType.TAG_OPEN],
3925
- [MATCH_TEXT, State.TEXT, exports.TokenType.TEXT],
3926
- [MATCH_TAG_LOOKAHEAD, State.TEXT, exports.TokenType.TEXT],
3975
+ [MATCH_DIRECTIVE, State.TEXT, TokenType.DIRECTIVE],
3976
+ [MATCH_CONDITIONAL, State.TEXT, TokenType.CONDITIONAL],
3977
+ [MATCH_COMMENT, State.TEXT, TokenType.COMMENT],
3978
+ [MATCH_TEMPLATING, State.TEXT, TokenType.TEMPLATING],
3979
+ [MATCH_TAG_OPEN, State.TAG, TokenType.TAG_OPEN],
3980
+ [MATCH_TEXT, State.TEXT, TokenType.TEXT],
3981
+ [MATCH_TAG_LOOKAHEAD, State.TEXT, TokenType.TEXT],
3927
3982
  ], 'expected text or "<"');
3928
3983
  }
3929
3984
  *tokenizeCDATA(context) {
@@ -3931,26 +3986,26 @@ class Lexer {
3931
3986
  }
3932
3987
  *tokenizeScript(context) {
3933
3988
  yield* this.match(context, [
3934
- [MATCH_SCRIPT_END, State.TAG, exports.TokenType.TAG_OPEN],
3935
- [MATCH_SCRIPT_DATA, State.SCRIPT, exports.TokenType.SCRIPT],
3989
+ [MATCH_SCRIPT_END, State.TAG, TokenType.TAG_OPEN],
3990
+ [MATCH_SCRIPT_DATA, State.SCRIPT, TokenType.SCRIPT],
3936
3991
  ], "expected </script>");
3937
3992
  }
3938
3993
  *tokenizeStyle(context) {
3939
3994
  yield* this.match(context, [
3940
- [MATCH_STYLE_END, State.TAG, exports.TokenType.TAG_OPEN],
3941
- [MATCH_STYLE_DATA, State.STYLE, exports.TokenType.STYLE],
3995
+ [MATCH_STYLE_END, State.TAG, TokenType.TAG_OPEN],
3996
+ [MATCH_STYLE_DATA, State.STYLE, TokenType.STYLE],
3942
3997
  ], "expected </style>");
3943
3998
  }
3944
3999
  }
3945
4000
 
3946
4001
  const whitespace = /(\s+)/;
3947
4002
  function isRelevant$3(event) {
3948
- return event.type === exports.TokenType.ATTR_VALUE;
4003
+ return event.type === TokenType.ATTR_VALUE;
3949
4004
  }
3950
4005
  class AttrDelimiter extends Rule {
3951
4006
  documentation() {
3952
4007
  return {
3953
- description: `Attribute value should be separated by `,
4008
+ description: `Attribute value must not be separated by whitespace.`,
3954
4009
  url: ruleDocumentationUrl("@/rules/attr-delimiter.ts"),
3955
4010
  };
3956
4011
  }
@@ -3967,7 +4022,7 @@ class AttrDelimiter extends Rule {
3967
4022
  }
3968
4023
 
3969
4024
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
3970
- const defaults$n = {
4025
+ const defaults$o = {
3971
4026
  pattern: DEFAULT_PATTERN,
3972
4027
  ignoreForeign: true,
3973
4028
  };
@@ -4004,7 +4059,7 @@ function generateDescription(name, pattern) {
4004
4059
  }
4005
4060
  class AttrPattern extends Rule {
4006
4061
  constructor(options) {
4007
- super({ ...defaults$n, ...options });
4062
+ super({ ...defaults$o, ...options });
4008
4063
  this.pattern = generateRegexp(this.options.pattern);
4009
4064
  }
4010
4065
  static schema() {
@@ -4064,13 +4119,13 @@ var QuoteStyle;
4064
4119
  QuoteStyle["DOUBLE_QUOTE"] = "\"";
4065
4120
  QuoteStyle["AUTO_QUOTE"] = "auto";
4066
4121
  })(QuoteStyle || (QuoteStyle = {}));
4067
- const defaults$m = {
4122
+ const defaults$n = {
4068
4123
  style: "auto",
4069
4124
  unquoted: false,
4070
4125
  };
4071
4126
  class AttrQuotes extends Rule {
4072
4127
  constructor(options) {
4073
- super({ ...defaults$m, ...options });
4128
+ super({ ...defaults$n, ...options });
4074
4129
  this.style = parseStyle$4(this.options.style);
4075
4130
  }
4076
4131
  static schema() {
@@ -4149,7 +4204,7 @@ class AttrSpacing extends Rule {
4149
4204
  setup() {
4150
4205
  let previousToken;
4151
4206
  this.on("token", (event) => {
4152
- if (event.type === exports.TokenType.ATTR_NAME && previousToken !== exports.TokenType.WHITESPACE) {
4207
+ if (event.type === TokenType.ATTR_NAME && previousToken !== TokenType.WHITESPACE) {
4153
4208
  this.report(null, "No space between attributes", event.location);
4154
4209
  }
4155
4210
  previousToken = event.type;
@@ -4235,12 +4290,12 @@ class AttributeAllowedValues extends Rule {
4235
4290
  }
4236
4291
  }
4237
4292
 
4238
- const defaults$l = {
4293
+ const defaults$m = {
4239
4294
  style: "omit",
4240
4295
  };
4241
4296
  class AttributeBooleanStyle extends Rule {
4242
4297
  constructor(options) {
4243
- super({ ...defaults$l, ...options });
4298
+ super({ ...defaults$m, ...options });
4244
4299
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4245
4300
  }
4246
4301
  static schema() {
@@ -4316,12 +4371,12 @@ function reportMessage$1(attr, style) {
4316
4371
  return "";
4317
4372
  }
4318
4373
 
4319
- const defaults$k = {
4374
+ const defaults$l = {
4320
4375
  style: "omit",
4321
4376
  };
4322
4377
  class AttributeEmptyStyle extends Rule {
4323
4378
  constructor(options) {
4324
- super({ ...defaults$k, ...options });
4379
+ super({ ...defaults$l, ...options });
4325
4380
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4326
4381
  }
4327
4382
  static schema() {
@@ -4429,12 +4484,12 @@ function describePattern(pattern) {
4429
4484
  }
4430
4485
  }
4431
4486
 
4432
- const defaults$j = {
4487
+ const defaults$k = {
4433
4488
  pattern: "kebabcase",
4434
4489
  };
4435
4490
  class ClassPattern extends Rule {
4436
4491
  constructor(options) {
4437
- super({ ...defaults$j, ...options });
4492
+ super({ ...defaults$k, ...options });
4438
4493
  this.pattern = parsePattern(this.options.pattern);
4439
4494
  }
4440
4495
  static schema() {
@@ -4460,7 +4515,9 @@ class ClassPattern extends Rule {
4460
4515
  classes.forEach((cur, index) => {
4461
4516
  if (!cur.match(this.pattern)) {
4462
4517
  const location = classes.location(index);
4463
- this.report(event.target, `Class "${cur}" does not match required pattern "${this.pattern}"`, location);
4518
+ const pattern = this.pattern.toString();
4519
+ const message = `Class "${cur}" does not match required pattern "${pattern}"`;
4520
+ this.report(event.target, message, location);
4464
4521
  }
4465
4522
  });
4466
4523
  });
@@ -4541,13 +4598,13 @@ class CloseOrder extends Rule {
4541
4598
  }
4542
4599
  }
4543
4600
 
4544
- const defaults$i = {
4601
+ const defaults$j = {
4545
4602
  include: null,
4546
4603
  exclude: null,
4547
4604
  };
4548
4605
  class Deprecated extends Rule {
4549
4606
  constructor(options) {
4550
- super({ ...defaults$i, ...options });
4607
+ super({ ...defaults$j, ...options });
4551
4608
  }
4552
4609
  static schema() {
4553
4610
  return {
@@ -4710,12 +4767,12 @@ class NoStyleTag$1 extends Rule {
4710
4767
  }
4711
4768
  }
4712
4769
 
4713
- const defaults$h = {
4770
+ const defaults$i = {
4714
4771
  style: "uppercase",
4715
4772
  };
4716
4773
  class DoctypeStyle extends Rule {
4717
4774
  constructor(options) {
4718
- super({ ...defaults$h, ...options });
4775
+ super({ ...defaults$i, ...options });
4719
4776
  }
4720
4777
  static schema() {
4721
4778
  return {
@@ -4747,12 +4804,12 @@ class DoctypeStyle extends Rule {
4747
4804
  }
4748
4805
  }
4749
4806
 
4750
- const defaults$g = {
4807
+ const defaults$h = {
4751
4808
  style: "lowercase",
4752
4809
  };
4753
4810
  class ElementCase extends Rule {
4754
4811
  constructor(options) {
4755
- super({ ...defaults$g, ...options });
4812
+ super({ ...defaults$h, ...options });
4756
4813
  this.style = new CaseStyle(this.options.style, "element-case");
4757
4814
  }
4758
4815
  static schema() {
@@ -4776,8 +4833,11 @@ class ElementCase extends Rule {
4776
4833
  };
4777
4834
  }
4778
4835
  documentation() {
4836
+ const { style } = this.options;
4779
4837
  return {
4780
- description: `Element tagname must be ${this.options.style}.`,
4838
+ description: Array.isArray(style)
4839
+ ? [`Element tagname must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
4840
+ : `Element tagname must be in ${style}.`,
4781
4841
  url: ruleDocumentationUrl("@/rules/element-case.ts"),
4782
4842
  };
4783
4843
  }
@@ -4815,14 +4875,14 @@ class ElementCase extends Rule {
4815
4875
  }
4816
4876
  }
4817
4877
 
4818
- const defaults$f = {
4878
+ const defaults$g = {
4819
4879
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
4820
4880
  whitelist: [],
4821
4881
  blacklist: [],
4822
4882
  };
4823
4883
  class ElementName extends Rule {
4824
4884
  constructor(options) {
4825
- super({ ...defaults$f, ...options });
4885
+ super({ ...defaults$g, ...options });
4826
4886
  // eslint-disable-next-line security/detect-non-literal-regexp
4827
4887
  this.pattern = new RegExp(this.options.pattern);
4828
4888
  }
@@ -4863,7 +4923,7 @@ class ElementName extends Rule {
4863
4923
  ...context.blacklist.map((cur) => `- ${cur}`),
4864
4924
  ];
4865
4925
  }
4866
- if (context.pattern !== defaults$f.pattern) {
4926
+ if (context.pattern !== defaults$g.pattern) {
4867
4927
  return [
4868
4928
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
4869
4929
  "",
@@ -5265,7 +5325,7 @@ class EmptyTitle extends Rule {
5265
5325
  }
5266
5326
  }
5267
5327
 
5268
- const defaults$e = {
5328
+ const defaults$f = {
5269
5329
  allowMultipleH1: false,
5270
5330
  minInitialRank: "h1",
5271
5331
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5296,7 +5356,7 @@ function parseMaxInitial(value) {
5296
5356
  }
5297
5357
  class HeadingLevel extends Rule {
5298
5358
  constructor(options) {
5299
- super({ ...defaults$e, ...options });
5359
+ super({ ...defaults$f, ...options });
5300
5360
  this.stack = [];
5301
5361
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5302
5362
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5454,12 +5514,12 @@ class HeadingLevel extends Rule {
5454
5514
  }
5455
5515
  }
5456
5516
 
5457
- const defaults$d = {
5517
+ const defaults$e = {
5458
5518
  pattern: "kebabcase",
5459
5519
  };
5460
5520
  class IdPattern extends Rule {
5461
5521
  constructor(options) {
5462
- super({ ...defaults$d, ...options });
5522
+ super({ ...defaults$e, ...options });
5463
5523
  this.pattern = parsePattern(this.options.pattern);
5464
5524
  }
5465
5525
  static schema() {
@@ -5478,6 +5538,7 @@ class IdPattern extends Rule {
5478
5538
  }
5479
5539
  setup() {
5480
5540
  this.on("attr", (event) => {
5541
+ var _a;
5481
5542
  if (event.key.toLowerCase() !== "id") {
5482
5543
  return;
5483
5544
  }
@@ -5486,7 +5547,10 @@ class IdPattern extends Rule {
5486
5547
  return;
5487
5548
  }
5488
5549
  if (!event.value || !event.value.match(this.pattern)) {
5489
- this.report(event.target, `ID "${event.value}" does not match required pattern "${this.pattern}"`, event.valueLocation);
5550
+ const value = (_a = event.value) !== null && _a !== void 0 ? _a : "";
5551
+ const pattern = this.pattern.toString();
5552
+ const message = `ID "${value}" does not match required pattern "${pattern}"`;
5553
+ this.report(event.target, message, event.valueLocation);
5490
5554
  }
5491
5555
  });
5492
5556
  }
@@ -5806,12 +5870,12 @@ function findLabelByParent(el) {
5806
5870
  return [];
5807
5871
  }
5808
5872
 
5809
- const defaults$c = {
5873
+ const defaults$d = {
5810
5874
  maxlength: 70,
5811
5875
  };
5812
5876
  class LongTitle extends Rule {
5813
5877
  constructor(options) {
5814
- super({ ...defaults$c, ...options });
5878
+ super({ ...defaults$d, ...options });
5815
5879
  this.maxlength = this.options.maxlength;
5816
5880
  }
5817
5881
  static schema() {
@@ -5958,13 +6022,13 @@ class MultipleLabeledControls extends Rule {
5958
6022
  }
5959
6023
  }
5960
6024
 
5961
- const defaults$b = {
6025
+ const defaults$c = {
5962
6026
  include: null,
5963
6027
  exclude: null,
5964
6028
  };
5965
6029
  class NoAutoplay extends Rule {
5966
6030
  constructor(options) {
5967
- super({ ...defaults$b, ...options });
6031
+ super({ ...defaults$c, ...options });
5968
6032
  }
5969
6033
  documentation(context) {
5970
6034
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6205,24 +6269,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6205
6269
  }
6206
6270
  }
6207
6271
 
6208
- const defaults$a = {
6272
+ const defaults$b = {
6209
6273
  include: null,
6210
6274
  exclude: null,
6211
6275
  allowedProperties: ["display"],
6212
6276
  };
6213
- function getCSSDeclarations(value) {
6214
- return value
6215
- .trim()
6216
- .split(";")
6217
- .filter(Boolean)
6218
- .map((it) => {
6219
- const [property, value] = it.split(":", 2);
6220
- return { property: property.trim(), value: value ? value.trim() : undefined };
6221
- });
6222
- }
6223
6277
  class NoInlineStyle extends Rule {
6224
6278
  constructor(options) {
6225
- super({ ...defaults$a, ...options });
6279
+ super({ ...defaults$b, ...options });
6226
6280
  }
6227
6281
  static schema() {
6228
6282
  return {
@@ -6300,18 +6354,15 @@ class NoInlineStyle extends Rule {
6300
6354
  return true;
6301
6355
  }
6302
6356
  allPropertiesAllowed(value) {
6303
- if (typeof value !== "string") {
6304
- return false;
6305
- }
6306
6357
  const allowProperties = this.options.allowedProperties;
6307
6358
  /* quick path: no properties are allowed, no need to check each one individually */
6308
6359
  if (allowProperties.length === 0) {
6309
6360
  return false;
6310
6361
  }
6311
- const declarations = getCSSDeclarations(value);
6362
+ const declarations = Object.keys(parseCssDeclaration(value));
6312
6363
  return (declarations.length > 0 &&
6313
6364
  declarations.every((it) => {
6314
- return allowProperties.includes(it.property);
6365
+ return allowProperties.includes(it);
6315
6366
  }));
6316
6367
  }
6317
6368
  }
@@ -6427,24 +6478,24 @@ class NoMultipleMain extends Rule {
6427
6478
  }
6428
6479
  }
6429
6480
 
6430
- const defaults$9 = {
6481
+ const defaults$a = {
6431
6482
  relaxed: false,
6432
6483
  };
6433
6484
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
6434
6485
  const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
6435
6486
  const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
6436
- const replacementTable = new Map([
6437
- ['"', "&quot;"],
6438
- ["&", "&amp;"],
6439
- ["'", "&apos;"],
6440
- ["<", "&lt;"],
6441
- ["=", "&equals;"],
6442
- [">", "&gt;"],
6443
- ["`", "&grave;"],
6444
- ]);
6487
+ const replacementTable = {
6488
+ '"': "&quot;",
6489
+ "&": "&amp;",
6490
+ "'": "&apos;",
6491
+ "<": "&lt;",
6492
+ "=": "&equals;",
6493
+ ">": "&gt;",
6494
+ "`": "&grave;",
6495
+ };
6445
6496
  class NoRawCharacters extends Rule {
6446
6497
  constructor(options) {
6447
- super({ ...defaults$9, ...options });
6498
+ super({ ...defaults$a, ...options });
6448
6499
  this.relaxed = this.options.relaxed;
6449
6500
  }
6450
6501
  static schema() {
@@ -6494,7 +6545,6 @@ class NoRawCharacters extends Rule {
6494
6545
  * @param text - The full text to find unescaped raw characters in.
6495
6546
  * @param location - Location of text.
6496
6547
  * @param regexp - Regexp pattern to match using.
6497
- * @param ignore - List of characters to ignore for this text.
6498
6548
  */
6499
6549
  findRawChars(node, text, location, regexp) {
6500
6550
  let match;
@@ -6510,7 +6560,7 @@ class NoRawCharacters extends Rule {
6510
6560
  continue;
6511
6561
  }
6512
6562
  /* determine replacement character and location */
6513
- const replacement = replacementTable.get(char);
6563
+ const replacement = replacementTable[char];
6514
6564
  const charLocation = sliceLocation(location, match.index, match.index + 1);
6515
6565
  /* report as error */
6516
6566
  this.report(node, `Raw "${char}" must be encoded as "${replacement}"`, charLocation);
@@ -6623,13 +6673,13 @@ class NoRedundantRole extends Rule {
6623
6673
  }
6624
6674
 
6625
6675
  const xmlns = /^(.+):.+$/;
6626
- const defaults$8 = {
6676
+ const defaults$9 = {
6627
6677
  ignoreForeign: true,
6628
6678
  ignoreXML: true,
6629
6679
  };
6630
6680
  class NoSelfClosing extends Rule {
6631
6681
  constructor(options) {
6632
- super({ ...defaults$8, ...options });
6682
+ super({ ...defaults$9, ...options });
6633
6683
  }
6634
6684
  static schema() {
6635
6685
  return {
@@ -6745,7 +6795,7 @@ class NoUtf8Bom extends Rule {
6745
6795
  }
6746
6796
  setup() {
6747
6797
  const unregister = this.on("token", (event) => {
6748
- if (event.type === exports.TokenType.UNICODE_BOM) {
6798
+ if (event.type === TokenType.UNICODE_BOM) {
6749
6799
  this.report(null, "File should be saved without UTF-8 BOM", event.location);
6750
6800
  }
6751
6801
  /* since the BOM must be the very first thing the rule can now be disabled for the rest of the run */
@@ -6762,13 +6812,13 @@ const replacement = {
6762
6812
  reset: '<button type="reset">',
6763
6813
  image: '<button type="button">',
6764
6814
  };
6765
- const defaults$7 = {
6815
+ const defaults$8 = {
6766
6816
  include: null,
6767
6817
  exclude: null,
6768
6818
  };
6769
6819
  class PreferButton extends Rule {
6770
6820
  constructor(options) {
6771
- super({ ...defaults$7, ...options });
6821
+ super({ ...defaults$8, ...options });
6772
6822
  }
6773
6823
  static schema() {
6774
6824
  return {
@@ -6843,7 +6893,7 @@ class PreferButton extends Rule {
6843
6893
  }
6844
6894
  }
6845
6895
 
6846
- const defaults$6 = {
6896
+ const defaults$7 = {
6847
6897
  mapping: {
6848
6898
  article: "article",
6849
6899
  banner: "header",
@@ -6873,7 +6923,7 @@ const defaults$6 = {
6873
6923
  };
6874
6924
  class PreferNativeElement extends Rule {
6875
6925
  constructor(options) {
6876
- super({ ...defaults$6, ...options });
6926
+ super({ ...defaults$7, ...options });
6877
6927
  }
6878
6928
  static schema() {
6879
6929
  return {
@@ -6993,7 +7043,7 @@ class PreferTbody extends Rule {
6993
7043
  }
6994
7044
  }
6995
7045
 
6996
- const defaults$5 = {
7046
+ const defaults$6 = {
6997
7047
  target: "all",
6998
7048
  };
6999
7049
  const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
@@ -7003,7 +7053,7 @@ const supportSri = {
7003
7053
  };
7004
7054
  class RequireSri extends Rule {
7005
7055
  constructor(options) {
7006
- super({ ...defaults$5, ...options });
7056
+ super({ ...defaults$6, ...options });
7007
7057
  this.target = this.options.target;
7008
7058
  }
7009
7059
  static schema() {
@@ -7131,6 +7181,175 @@ class SvgFocusable extends Rule {
7131
7181
  }
7132
7182
  }
7133
7183
 
7184
+ const defaults$5 = {
7185
+ characters: [
7186
+ { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
7187
+ { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" },
7188
+ ],
7189
+ ignoreClasses: [],
7190
+ ignoreStyle: true,
7191
+ };
7192
+ function constructRegex(characters) {
7193
+ const disallowed = characters
7194
+ .map((it) => {
7195
+ return it.pattern;
7196
+ })
7197
+ .join("|");
7198
+ const pattern = `(${disallowed})`;
7199
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7200
+ return new RegExp(pattern, "g");
7201
+ }
7202
+ function getText(node) {
7203
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
7204
+ const match = node.textContent.match(/^(\s*)(.*)$/);
7205
+ const [, leading, text] = match;
7206
+ return [leading.length, text.trimEnd()];
7207
+ }
7208
+ /**
7209
+ * Node 12 does not support String.matchAll, this simulates it's behavior.
7210
+ */
7211
+ function matchAll(text, regexp) {
7212
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7213
+ const copy = new RegExp(regexp);
7214
+ const matches = [];
7215
+ /* eslint-disable-next-line no-constant-condition */
7216
+ while (true) {
7217
+ const match = copy.exec(text);
7218
+ if (match === null) {
7219
+ break;
7220
+ }
7221
+ matches.push(match);
7222
+ }
7223
+ return matches;
7224
+ }
7225
+ class TelNonBreaking extends Rule {
7226
+ constructor(options) {
7227
+ super({ ...defaults$5, ...options });
7228
+ this.regex = constructRegex(this.options.characters);
7229
+ }
7230
+ static schema() {
7231
+ return {
7232
+ characters: {
7233
+ type: "array",
7234
+ items: {
7235
+ type: "object",
7236
+ additionalProperties: false,
7237
+ properties: {
7238
+ pattern: {
7239
+ type: "string",
7240
+ },
7241
+ replacement: {
7242
+ type: "string",
7243
+ },
7244
+ description: {
7245
+ type: "string",
7246
+ },
7247
+ },
7248
+ },
7249
+ },
7250
+ ignoreClasses: {
7251
+ type: "array",
7252
+ items: {
7253
+ type: "string",
7254
+ },
7255
+ },
7256
+ ignoreStyle: {
7257
+ type: "boolean",
7258
+ },
7259
+ };
7260
+ }
7261
+ documentation(context) {
7262
+ const { characters } = this.options;
7263
+ const replacements = characters.map((it) => {
7264
+ return ` - \`${it.pattern}\` - replace with \`${it.replacement}\` (${it.description}).`;
7265
+ });
7266
+ return {
7267
+ description: [
7268
+ context
7269
+ ? `The \`${context.pattern}\` character should be replaced with \`${context.replacement}\` character (${context.description}) when used in a telephone number.`
7270
+ : `Replace this character with a non-breaking version.`,
7271
+ "",
7272
+ "Unless non-breaking characters is used there could be a line break inserted at that character.",
7273
+ "Line breaks make is harder to read and understand the telephone number.",
7274
+ "",
7275
+ "The following characters should be avoided:",
7276
+ "",
7277
+ ...replacements,
7278
+ ].join("\n"),
7279
+ url: ruleDocumentationUrl("@/rules/tel-non-breaking.ts"),
7280
+ };
7281
+ }
7282
+ setup() {
7283
+ this.on("element:ready", this.isRelevant, (event) => {
7284
+ const { target } = event;
7285
+ if (this.isIgnored(target)) {
7286
+ return;
7287
+ }
7288
+ this.walk(target, target);
7289
+ });
7290
+ }
7291
+ isRelevant(event) {
7292
+ const { target } = event;
7293
+ /* should only deal with anchors */
7294
+ if (!target.is("a")) {
7295
+ return false;
7296
+ }
7297
+ /* ignore if anchor does not have tel href */
7298
+ const attr = target.getAttribute("href");
7299
+ if (!attr || !attr.valueMatches(/^tel:/, false)) {
7300
+ return false;
7301
+ }
7302
+ return true;
7303
+ }
7304
+ isIgnoredClass(node) {
7305
+ const { ignoreClasses } = this.options;
7306
+ const { classList } = node;
7307
+ return ignoreClasses.some((it) => classList.contains(it));
7308
+ }
7309
+ isIgnoredStyle(node) {
7310
+ const { ignoreStyle } = this.options;
7311
+ const { style } = node;
7312
+ if (!ignoreStyle) {
7313
+ return false;
7314
+ }
7315
+ if (style["white-space"] === "nowrap" || style["white-space"] === "pre") {
7316
+ return true;
7317
+ }
7318
+ return false;
7319
+ }
7320
+ isIgnored(node) {
7321
+ return this.isIgnoredClass(node) || this.isIgnoredStyle(node);
7322
+ }
7323
+ walk(anchor, node) {
7324
+ for (const child of node.childNodes) {
7325
+ if (isTextNode(child)) {
7326
+ this.detectDisallowed(anchor, child);
7327
+ }
7328
+ else if (isElementNode(child)) {
7329
+ this.walk(anchor, child);
7330
+ }
7331
+ }
7332
+ }
7333
+ detectDisallowed(anchor, node) {
7334
+ const [offset, text] = getText(node);
7335
+ const matches = matchAll(text, this.regex);
7336
+ for (const match of matches) {
7337
+ const detected = match[0];
7338
+ const entry = this.options.characters.find((it) => it.pattern === detected);
7339
+ /* istanbul ignore next: should never happen and cannot be tested, just a sanity check */
7340
+ if (!entry) {
7341
+ throw new Error(`Failed to find entry for "${detected}" when searching text "${text}"`);
7342
+ }
7343
+ const message = `"${detected}" should be replaced with "${entry.replacement}" (${entry.description}) in telephone number`;
7344
+ const begin = offset + match.index;
7345
+ const end = begin + detected.length;
7346
+ const location = sliceLocation(node.location, begin, end);
7347
+ const context = entry;
7348
+ this.report(anchor, message, location, context);
7349
+ }
7350
+ }
7351
+ }
7352
+
7134
7353
  function hasAltText(image) {
7135
7354
  const alt = image.getAttribute("alt");
7136
7355
  /* missing or boolean */
@@ -9161,9 +9380,6 @@ function parseStyle$1(name) {
9161
9380
  case "selfclose":
9162
9381
  case "selfclosing":
9163
9382
  return Style$1.AlwaysSelfclose;
9164
- /* istanbul ignore next: covered by schema validation */
9165
- default:
9166
- throw new Error(`Invalid style "${name}" for "void" rule`);
9167
9383
  }
9168
9384
  }
9169
9385
 
@@ -9595,6 +9811,7 @@ const bundledRules = {
9595
9811
  "script-element": ScriptElement,
9596
9812
  "script-type": ScriptType,
9597
9813
  "svg-focusable": SvgFocusable,
9814
+ "tel-non-breaking": TelNonBreaking,
9598
9815
  "text-content": TextContent,
9599
9816
  "unrecognized-char-ref": UnknownCharReference,
9600
9817
  void: Void,
@@ -9691,6 +9908,7 @@ const config$1 = {
9691
9908
  "script-element": "error",
9692
9909
  "script-type": "error",
9693
9910
  "svg-focusable": "off",
9911
+ "tel-non-breaking": "error",
9694
9912
  "text-content": "error",
9695
9913
  "unrecognized-char-ref": "error",
9696
9914
  void: "off",
@@ -9796,7 +10014,8 @@ class ResolvedConfig {
9796
10014
  });
9797
10015
  }
9798
10016
  catch (err) {
9799
- throw new NestedError(`When transforming "${source.filename}": ${err.message}`, err);
10017
+ const message = err instanceof Error ? err.message : String(err);
10018
+ throw new NestedError(`When transforming "${source.filename}": ${message}`, err);
9800
10019
  }
9801
10020
  }
9802
10021
  else {
@@ -9872,9 +10091,10 @@ function loadFromFile(filename) {
9872
10091
  }
9873
10092
  /* expand any relative paths */
9874
10093
  for (const key of ["extends", "elements", "plugins"]) {
9875
- if (!json[key])
10094
+ const value = json[key];
10095
+ if (!value)
9876
10096
  continue;
9877
- json[key] = json[key].map((ref) => {
10097
+ json[key] = value.map((ref) => {
9878
10098
  return Config.expandRelative(ref, path__default["default"].dirname(filename));
9879
10099
  });
9880
10100
  }
@@ -10032,6 +10252,7 @@ class Config {
10032
10252
  /**
10033
10253
  * Get element metadata.
10034
10254
  */
10255
+ /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10035
10256
  getMetaTable() {
10036
10257
  /* use cached table if it exists */
10037
10258
  if (this.metaTable) {
@@ -10054,7 +10275,7 @@ class Config {
10054
10275
  }
10055
10276
  let filename;
10056
10277
  /* try searching builtin metadata */
10057
- filename = path__default["default"].join(projectRoot, "elements", `${entry}.json`);
10278
+ filename = path__default["default"].join(projectRoot, "elements", `${entry}.js`);
10058
10279
  if (fs__default["default"].existsSync(filename)) {
10059
10280
  metaTable.loadFromFile(filename);
10060
10281
  continue;
@@ -10070,7 +10291,8 @@ class Config {
10070
10291
  metaTable.loadFromObject(legacyRequire(entry));
10071
10292
  }
10072
10293
  catch (err) {
10073
- throw new ConfigError(`Failed to load elements from "${entry}": ${err.message}`, err);
10294
+ const message = err instanceof Error ? err.message : String(err);
10295
+ throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, err);
10074
10296
  }
10075
10297
  }
10076
10298
  metaTable.init();
@@ -10146,7 +10368,8 @@ class Config {
10146
10368
  return plugin;
10147
10369
  }
10148
10370
  catch (err) {
10149
- throw new ConfigError(`Failed to load plugin "${moduleName}": ${err}`, err);
10371
+ const message = err instanceof Error ? err.message : String(err);
10372
+ throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, err);
10150
10373
  }
10151
10374
  });
10152
10375
  }
@@ -10466,7 +10689,7 @@ class ParserError extends Error {
10466
10689
  }
10467
10690
 
10468
10691
  function isAttrValueToken(token) {
10469
- return Boolean(token && token.type === exports.TokenType.ATTR_VALUE);
10692
+ return Boolean(token && token.type === TokenType.ATTR_VALUE);
10470
10693
  }
10471
10694
  function svgShouldRetainTag(foreignTagName, tagName) {
10472
10695
  return foreignTagName === "svg" && ["title", "desc"].includes(tagName);
@@ -10574,43 +10797,43 @@ class Parser {
10574
10797
  /* eslint-disable-next-line complexity */
10575
10798
  consume(source, token, tokenStream) {
10576
10799
  switch (token.type) {
10577
- case exports.TokenType.UNICODE_BOM:
10800
+ case TokenType.UNICODE_BOM:
10578
10801
  /* ignore */
10579
10802
  break;
10580
- case exports.TokenType.TAG_OPEN:
10803
+ case TokenType.TAG_OPEN:
10581
10804
  this.consumeTag(source, token, tokenStream);
10582
10805
  break;
10583
- case exports.TokenType.WHITESPACE:
10806
+ case TokenType.WHITESPACE:
10584
10807
  this.trigger("whitespace", {
10585
10808
  text: token.data[0],
10586
10809
  location: token.location,
10587
10810
  });
10588
10811
  this.appendText(token.data[0], token.location);
10589
10812
  break;
10590
- case exports.TokenType.DIRECTIVE:
10813
+ case TokenType.DIRECTIVE:
10591
10814
  this.consumeDirective(token);
10592
10815
  break;
10593
- case exports.TokenType.CONDITIONAL:
10816
+ case TokenType.CONDITIONAL:
10594
10817
  this.consumeConditional(token);
10595
10818
  break;
10596
- case exports.TokenType.COMMENT:
10819
+ case TokenType.COMMENT:
10597
10820
  this.consumeComment(token);
10598
10821
  break;
10599
- case exports.TokenType.DOCTYPE_OPEN:
10822
+ case TokenType.DOCTYPE_OPEN:
10600
10823
  this.consumeDoctype(token, tokenStream);
10601
10824
  break;
10602
- case exports.TokenType.TEXT:
10603
- case exports.TokenType.TEMPLATING:
10825
+ case TokenType.TEXT:
10826
+ case TokenType.TEMPLATING:
10604
10827
  this.appendText(token.data[0], token.location);
10605
10828
  break;
10606
- case exports.TokenType.EOF:
10829
+ case TokenType.EOF:
10607
10830
  this.closeTree(source, token.location);
10608
10831
  break;
10609
10832
  }
10610
10833
  }
10611
10834
  /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10612
10835
  consumeTag(source, startToken, tokenStream) {
10613
- const tokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.TAG_CLOSE, startToken.location));
10836
+ const tokens = Array.from(this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location));
10614
10837
  const endToken = tokens.slice(-1)[0];
10615
10838
  const closeOptional = this.closeOptional(startToken);
10616
10839
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
@@ -10636,9 +10859,9 @@ class Parser {
10636
10859
  for (let i = 0; i < tokens.length; i++) {
10637
10860
  const token = tokens[i];
10638
10861
  switch (token.type) {
10639
- case exports.TokenType.WHITESPACE:
10862
+ case TokenType.WHITESPACE:
10640
10863
  break;
10641
- case exports.TokenType.ATTR_NAME:
10864
+ case TokenType.ATTR_NAME:
10642
10865
  this.consumeAttribute(source, node, token, tokens[i + 1]);
10643
10866
  break;
10644
10867
  }
@@ -10716,7 +10939,7 @@ class Parser {
10716
10939
  let endToken;
10717
10940
  do {
10718
10941
  /* search for tags */
10719
- const tokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.TAG_OPEN, errorLocation));
10942
+ const tokens = Array.from(this.consumeUntil(tokenStream, TokenType.TAG_OPEN, errorLocation));
10720
10943
  const [last] = tokens.slice(-1);
10721
10944
  const [, tagClosed, tagName] = last.data;
10722
10945
  /* special case: svg <title> and <desc> should be intact as it affects accessibility */
@@ -10733,7 +10956,7 @@ class Parser {
10733
10956
  continue;
10734
10957
  }
10735
10958
  /* locate end token and determine if this is a self-closed tag */
10736
- const endTokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.TAG_CLOSE, last.location));
10959
+ const endTokens = Array.from(this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location));
10737
10960
  endToken = endTokens.slice(-1)[0];
10738
10961
  const selfClosed = endToken.data[0] === "/>";
10739
10962
  /* since foreign element may be nested keep a count for the number of
@@ -10819,7 +11042,7 @@ class Parser {
10819
11042
  * ^^^ ^^^ ^^^ (null) (null)
10820
11043
  */
10821
11044
  getAttributeValueLocation(token) {
10822
- if (!token || token.type !== exports.TokenType.ATTR_VALUE || token.data[2] === "") {
11045
+ if (!token || token.type !== TokenType.ATTR_VALUE || token.data[2] === "") {
10823
11046
  return null;
10824
11047
  }
10825
11048
  const quote = token.data[3];
@@ -10837,7 +11060,7 @@ class Parser {
10837
11060
  getAttributeLocation(key, value) {
10838
11061
  var _a;
10839
11062
  const begin = key.location;
10840
- const end = value && value.type === exports.TokenType.ATTR_VALUE ? value.location : undefined;
11063
+ const end = value && value.type === TokenType.ATTR_VALUE ? value.location : undefined;
10841
11064
  return {
10842
11065
  filename: begin.filename,
10843
11066
  line: begin.line,
@@ -10898,7 +11121,7 @@ class Parser {
10898
11121
  * Consumes doctype tokens. Emits doctype event.
10899
11122
  */
10900
11123
  consumeDoctype(startToken, tokenStream) {
10901
- const tokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.DOCTYPE_CLOSE, startToken.location));
11124
+ const tokens = Array.from(this.consumeUntil(tokenStream, TokenType.DOCTYPE_CLOSE, startToken.location));
10902
11125
  /* first token is the doctype, second is the closing ">" */
10903
11126
  const doctype = tokens[0];
10904
11127
  const value = doctype.data[0];
@@ -10924,7 +11147,7 @@ class Parser {
10924
11147
  return;
10925
11148
  it = this.next(tokenStream);
10926
11149
  }
10927
- throw new ParserError(errorLocation, `stream ended before ${exports.TokenType[search]} token was found`);
11150
+ throw new ParserError(errorLocation, `stream ended before ${TokenType[search]} token was found`);
10928
11151
  }
10929
11152
  /**
10930
11153
  * Consumes tokens until a matching close-tag is found. Tags are appended to
@@ -10938,7 +11161,7 @@ class Parser {
10938
11161
  while (!it.done) {
10939
11162
  const token = it.value;
10940
11163
  this.consume(source, token, tokenStream);
10941
- if (token.type === exports.TokenType.TAG_OPEN) {
11164
+ if (token.type === TokenType.TAG_OPEN) {
10942
11165
  const [, close, tagName] = token.data;
10943
11166
  if (tagName === searchTag) {
10944
11167
  if (close) {
@@ -11011,6 +11234,12 @@ class Parser {
11011
11234
  }
11012
11235
  }
11013
11236
 
11237
+ function freeze(src) {
11238
+ return {
11239
+ ...src,
11240
+ selector: src.selector(),
11241
+ };
11242
+ }
11014
11243
  /**
11015
11244
  * @internal
11016
11245
  */
@@ -11062,7 +11291,9 @@ class Reporter {
11062
11291
  line: location.line,
11063
11292
  column: location.column,
11064
11293
  size: location.size || 0,
11065
- selector: node ? node.generateSelector() : null,
11294
+ selector() {
11295
+ return node ? node.generateSelector() : null;
11296
+ },
11066
11297
  context,
11067
11298
  });
11068
11299
  }
@@ -11076,7 +11307,7 @@ class Reporter {
11076
11307
  const report = {
11077
11308
  valid: this.isValid(),
11078
11309
  results: Object.keys(this.result).map((filePath) => {
11079
- const messages = Array.from(this.result[filePath]).sort(messageSort);
11310
+ const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
11080
11311
  const source = (sources || []).find((source) => { var _a; return filePath === ((_a = source.filename) !== null && _a !== void 0 ? _a : ""); });
11081
11312
  return {
11082
11313
  filePath,
@@ -11150,7 +11381,7 @@ class Engine {
11150
11381
  /**
11151
11382
  * Lint sources and return report
11152
11383
  *
11153
- * @param src - Parsed source.
11384
+ * @param sources - Sources to lint.
11154
11385
  * @returns Report output.
11155
11386
  */
11156
11387
  lint(sources) {
@@ -11229,7 +11460,7 @@ class Engine {
11229
11460
  for (const token of lexer.tokenize(src)) {
11230
11461
  const data = (_a = token.data[0]) !== null && _a !== void 0 ? _a : "";
11231
11462
  lines.push({
11232
- token: exports.TokenType[token.type],
11463
+ token: TokenType[token.type],
11233
11464
  data,
11234
11465
  location: `${token.location.filename}:${token.location.line}:${token.location.column}`,
11235
11466
  });
@@ -11244,7 +11475,7 @@ class Engine {
11244
11475
  const lines = [];
11245
11476
  function decoration(node) {
11246
11477
  let output = "";
11247
- if (node.hasAttribute("id")) {
11478
+ if (node.id) {
11248
11479
  output += `#${node.id}`;
11249
11480
  }
11250
11481
  if (node.hasAttribute("class")) {
@@ -11478,7 +11709,7 @@ class Engine {
11478
11709
  line: location.line,
11479
11710
  column: location.column,
11480
11711
  size: location.size || 0,
11481
- selector: null,
11712
+ selector: () => null,
11482
11713
  });
11483
11714
  }
11484
11715
  }
@@ -12000,6 +12231,8 @@ const formatter$4 = checkstyleFormatter;
12000
12231
 
12001
12232
  const defaults = {
12002
12233
  showLink: true,
12234
+ showSummary: true,
12235
+ showSelector: false,
12003
12236
  };
12004
12237
  /**
12005
12238
  * Codeframe formatter based on ESLint codeframe.
@@ -12055,6 +12288,7 @@ function getEndLocation(message, source) {
12055
12288
  * @returns The formatted output.
12056
12289
  */
12057
12290
  function formatMessage(message, parentResult, options) {
12291
+ var _a;
12058
12292
  const type = message.severity === 2 ? kleur__default["default"].red("error") : kleur__default["default"].yellow("warning");
12059
12293
  const msg = `${kleur__default["default"].bold(message.message.replace(/([^ ])\.$/, "$1"))}`;
12060
12294
  const ruleId = kleur__default["default"].dim(`(${message.ruleId})`);
@@ -12077,6 +12311,9 @@ function formatMessage(message, parentResult, options) {
12077
12311
  end: getEndLocation(message, sourceCode),
12078
12312
  }, { highlightCode: false }));
12079
12313
  }
12314
+ if (options.showSelector) {
12315
+ result.push(`${kleur__default["default"].bold("Selector:")} ${(_a = message.selector) !== null && _a !== void 0 ? _a : "-"}`);
12316
+ }
12080
12317
  if (options.showLink && message.ruleUrl) {
12081
12318
  result.push(`${kleur__default["default"].bold("Details:")} ${message.ruleUrl}`);
12082
12319
  }
@@ -12114,9 +12351,11 @@ function codeframe(results, options) {
12114
12351
  return resultsOutput.concat(messages);
12115
12352
  }, [])
12116
12353
  .join("\n");
12117
- output += "\n";
12118
- output += formatSummary(errors, warnings);
12119
- output += "\n";
12354
+ if (merged.showSummary) {
12355
+ output += "\n";
12356
+ output += formatSummary(errors, warnings);
12357
+ output += "\n";
12358
+ }
12120
12359
  return errors + warnings > 0 ? output : "";
12121
12360
  }
12122
12361
  const formatter$3 = codeframe;
@@ -12216,6 +12455,7 @@ exports.TemplateExtractor = TemplateExtractor;
12216
12455
  exports.TextNode = TextNode;
12217
12456
  exports.UserError = UserError;
12218
12457
  exports.bugs = bugs;
12458
+ exports.codeframe = codeframe;
12219
12459
  exports.compatibilityCheck = compatibilityCheck;
12220
12460
  exports.getFormatter = getFormatter;
12221
12461
  exports.legacyRequire = legacyRequire;