html-validate 6.5.0 → 6.7.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 +14 -5
  7. package/dist/cjs/core.js +362 -123
  8. package/dist/cjs/core.js.map +1 -1
  9. package/dist/cjs/html-validate.js +57 -5
  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.d.ts +2 -1
  15. package/dist/cjs/jest-lib.js +52 -27
  16. package/dist/cjs/jest-lib.js.map +1 -1
  17. package/dist/cjs/jest.d.ts +2 -2
  18. package/dist/cjs/jest.js +2 -0
  19. package/dist/cjs/jest.js.map +1 -1
  20. package/dist/cjs/test-utils.d.ts +1 -1
  21. package/dist/cjs/test-utils.js.map +1 -1
  22. package/dist/es/browser.d.ts +1 -1
  23. package/dist/es/browser.js +2 -0
  24. package/dist/es/browser.js.map +1 -1
  25. package/dist/es/cli.js +9 -5
  26. package/dist/es/cli.js.map +1 -1
  27. package/dist/es/core.d.ts +14 -5
  28. package/dist/es/core.js +340 -121
  29. package/dist/es/core.js.map +1 -1
  30. package/dist/es/html-validate.js +57 -5
  31. package/dist/es/html-validate.js.map +1 -1
  32. package/dist/es/index.d.ts +2 -2
  33. package/dist/es/index.js +2 -0
  34. package/dist/es/index.js.map +1 -1
  35. package/dist/es/jest-lib.d.ts +2 -1
  36. package/dist/es/jest-lib.js +52 -27
  37. package/dist/es/jest-lib.js.map +1 -1
  38. package/dist/es/jest.d.ts +2 -2
  39. package/dist/es/jest.js +2 -0
  40. package/dist/es/jest.js.map +1 -1
  41. package/dist/es/test-utils.d.ts +1 -1
  42. package/dist/es/test-utils.js.map +1 -1
  43. package/package.json +1 -152
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
  }
@@ -1849,8 +1871,13 @@ exports.NodeClosed = void 0;
1849
1871
  NodeClosed[NodeClosed["VoidSelfClosed"] = 3] = "VoidSelfClosed";
1850
1872
  NodeClosed[NodeClosed["ImplicitClosed"] = 4] = "ImplicitClosed";
1851
1873
  })(exports.NodeClosed || (exports.NodeClosed = {}));
1852
- function isElement(node) {
1853
- return node.nodeType === NodeType.ELEMENT_NODE;
1874
+ /**
1875
+ * Returns true if the node is an element node.
1876
+ *
1877
+ * @public
1878
+ */
1879
+ function isElementNode(node) {
1880
+ return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
1854
1881
  }
1855
1882
  function isValidTagName(tagName) {
1856
1883
  return Boolean(tagName !== "" && tagName !== "*");
@@ -1891,10 +1918,13 @@ class HtmlElement extends DOMNode {
1891
1918
  }
1892
1919
  /**
1893
1920
  * @internal
1921
+ *
1922
+ * @param namespace - If given it is appended to the tagName.
1894
1923
  */
1895
- static fromTokens(startToken, endToken, parent, metaTable) {
1896
- const tagName = startToken.data[2];
1897
- if (!tagName) {
1924
+ static fromTokens(startToken, endToken, parent, metaTable, namespace = "") {
1925
+ const name = startToken.data[2];
1926
+ const tagName = namespace ? `${namespace}:${name}` : name;
1927
+ if (!name) {
1898
1928
  throw new Error("tagName cannot be empty");
1899
1929
  }
1900
1930
  const meta = metaTable ? metaTable.getMetaFor(tagName) : null;
@@ -1921,7 +1951,7 @@ class HtmlElement extends DOMNode {
1921
1951
  * Similar to childNodes but only elements.
1922
1952
  */
1923
1953
  get childElements() {
1924
- return this.childNodes.filter(isElement);
1954
+ return this.childNodes.filter(isElementNode);
1925
1955
  }
1926
1956
  /**
1927
1957
  * Find the first ancestor matching a selector.
@@ -2182,8 +2212,9 @@ class HtmlElement extends DOMNode {
2182
2212
  }, []);
2183
2213
  }
2184
2214
  querySelector(selector) {
2215
+ var _a;
2185
2216
  const it = this.querySelectorImpl(selector);
2186
- return it.next().value || null;
2217
+ return (_a = it.next().value) !== null && _a !== void 0 ? _a : null; // eslint-disable-line @typescript-eslint/no-unsafe-return
2187
2218
  }
2188
2219
  querySelectorAll(selector) {
2189
2220
  const it = this.querySelectorImpl(selector);
@@ -2750,8 +2781,6 @@ var configurationSchema = {
2750
2781
  };
2751
2782
 
2752
2783
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2753
- const espree = legacyRequire("espree");
2754
- const walk = legacyRequire("acorn-walk");
2755
2784
  function joinTemplateLiteral(nodes) {
2756
2785
  let offset = nodes[0].start + 1;
2757
2786
  let output = "";
@@ -2867,7 +2896,7 @@ class TemplateExtractor {
2867
2896
  }
2868
2897
  static fromFilename(filename) {
2869
2898
  const source = fs__default["default"].readFileSync(filename, "utf-8");
2870
- const ast = espree.parse(source, {
2899
+ const ast = espree__namespace.parse(source, {
2871
2900
  ecmaVersion: 2017,
2872
2901
  sourceType: "module",
2873
2902
  loc: true,
@@ -2886,7 +2915,7 @@ class TemplateExtractor {
2886
2915
  * `Source`. Defauls to `"inline"`.
2887
2916
  */
2888
2917
  static fromString(source, filename) {
2889
- const ast = espree.parse(source, {
2918
+ const ast = espree__namespace.parse(source, {
2890
2919
  ecmaVersion: 2017,
2891
2920
  sourceType: "module",
2892
2921
  loc: true,
@@ -2932,7 +2961,8 @@ class TemplateExtractor {
2932
2961
  extractObjectProperty(key) {
2933
2962
  const result = [];
2934
2963
  const { filename, data } = this;
2935
- walk.simple(this.ast, {
2964
+ const node = this.ast;
2965
+ walk__namespace.simple(node, {
2936
2966
  Property(node) {
2937
2967
  if (compareKey(node.key, key, filename)) {
2938
2968
  const source = extractLiteral(node.value, filename, data);
@@ -2956,7 +2986,7 @@ var TRANSFORMER_API;
2956
2986
  /** @public */
2957
2987
  const name = "html-validate";
2958
2988
  /** @public */
2959
- const version = "6.5.0";
2989
+ const version = "6.7.0";
2960
2990
  /** @public */
2961
2991
  const homepage = "https://html-validate.org";
2962
2992
  /** @public */
@@ -3277,7 +3307,7 @@ function ruleDocumentationUrl(filename) {
3277
3307
  return `${homepage}/rules/${normalized}.html`;
3278
3308
  }
3279
3309
 
3280
- const defaults$p = {
3310
+ const defaults$q = {
3281
3311
  allowExternal: true,
3282
3312
  allowRelative: true,
3283
3313
  allowAbsolute: true,
@@ -3321,7 +3351,7 @@ function matchList(value, list) {
3321
3351
  }
3322
3352
  class AllowedLinks extends Rule {
3323
3353
  constructor(options) {
3324
- super({ ...defaults$p, ...options });
3354
+ super({ ...defaults$q, ...options });
3325
3355
  this.allowExternal = parseAllow(this.options.allowExternal);
3326
3356
  this.allowRelative = parseAllow(this.options.allowRelative);
3327
3357
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3600,7 +3630,7 @@ class CaseStyle {
3600
3630
  default: {
3601
3631
  const last = names.slice(-1);
3602
3632
  const rest = names.slice(0, -1);
3603
- return `${rest.join(", ")} or ${last}`;
3633
+ return `${rest.join(", ")} or ${last[0]}`;
3604
3634
  }
3605
3635
  }
3606
3636
  }
@@ -3616,19 +3646,19 @@ class CaseStyle {
3616
3646
  case "camelcase":
3617
3647
  return { pattern: /^[a-z][A-Za-z]*$/, name: "camelCase" };
3618
3648
  default:
3619
- throw new ConfigError(`Invalid style "${style}" for ${ruleId} rule`);
3649
+ throw new ConfigError(`Invalid style "${cur}" for ${ruleId} rule`);
3620
3650
  }
3621
3651
  });
3622
3652
  }
3623
3653
  }
3624
3654
 
3625
- const defaults$o = {
3655
+ const defaults$p = {
3626
3656
  style: "lowercase",
3627
3657
  ignoreForeign: true,
3628
3658
  };
3629
3659
  class AttrCase extends Rule {
3630
3660
  constructor(options) {
3631
- super({ ...defaults$o, ...options });
3661
+ super({ ...defaults$p, ...options });
3632
3662
  this.style = new CaseStyle(this.options.style, "attr-case");
3633
3663
  }
3634
3664
  static schema() {
@@ -3655,8 +3685,11 @@ class AttrCase extends Rule {
3655
3685
  };
3656
3686
  }
3657
3687
  documentation() {
3688
+ const { style } = this.options;
3658
3689
  return {
3659
- description: `Attribute name must be ${this.options.style}.`,
3690
+ description: Array.isArray(style)
3691
+ ? [`Attribute name must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
3692
+ : `Attribute name must be in ${style}.`,
3660
3693
  url: ruleDocumentationUrl("@/rules/attr-case.ts"),
3661
3694
  };
3662
3695
  }
@@ -3947,7 +3980,7 @@ function isRelevant$3(event) {
3947
3980
  class AttrDelimiter extends Rule {
3948
3981
  documentation() {
3949
3982
  return {
3950
- description: `Attribute value should be separated by `,
3983
+ description: `Attribute value must not be separated by whitespace.`,
3951
3984
  url: ruleDocumentationUrl("@/rules/attr-delimiter.ts"),
3952
3985
  };
3953
3986
  }
@@ -3964,7 +3997,7 @@ class AttrDelimiter extends Rule {
3964
3997
  }
3965
3998
 
3966
3999
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
3967
- const defaults$n = {
4000
+ const defaults$o = {
3968
4001
  pattern: DEFAULT_PATTERN,
3969
4002
  ignoreForeign: true,
3970
4003
  };
@@ -4001,7 +4034,7 @@ function generateDescription(name, pattern) {
4001
4034
  }
4002
4035
  class AttrPattern extends Rule {
4003
4036
  constructor(options) {
4004
- super({ ...defaults$n, ...options });
4037
+ super({ ...defaults$o, ...options });
4005
4038
  this.pattern = generateRegexp(this.options.pattern);
4006
4039
  }
4007
4040
  static schema() {
@@ -4061,13 +4094,13 @@ var QuoteStyle;
4061
4094
  QuoteStyle["DOUBLE_QUOTE"] = "\"";
4062
4095
  QuoteStyle["AUTO_QUOTE"] = "auto";
4063
4096
  })(QuoteStyle || (QuoteStyle = {}));
4064
- const defaults$m = {
4097
+ const defaults$n = {
4065
4098
  style: "auto",
4066
4099
  unquoted: false,
4067
4100
  };
4068
4101
  class AttrQuotes extends Rule {
4069
4102
  constructor(options) {
4070
- super({ ...defaults$m, ...options });
4103
+ super({ ...defaults$n, ...options });
4071
4104
  this.style = parseStyle$4(this.options.style);
4072
4105
  }
4073
4106
  static schema() {
@@ -4232,12 +4265,12 @@ class AttributeAllowedValues extends Rule {
4232
4265
  }
4233
4266
  }
4234
4267
 
4235
- const defaults$l = {
4268
+ const defaults$m = {
4236
4269
  style: "omit",
4237
4270
  };
4238
4271
  class AttributeBooleanStyle extends Rule {
4239
4272
  constructor(options) {
4240
- super({ ...defaults$l, ...options });
4273
+ super({ ...defaults$m, ...options });
4241
4274
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4242
4275
  }
4243
4276
  static schema() {
@@ -4313,12 +4346,12 @@ function reportMessage$1(attr, style) {
4313
4346
  return "";
4314
4347
  }
4315
4348
 
4316
- const defaults$k = {
4349
+ const defaults$l = {
4317
4350
  style: "omit",
4318
4351
  };
4319
4352
  class AttributeEmptyStyle extends Rule {
4320
4353
  constructor(options) {
4321
- super({ ...defaults$k, ...options });
4354
+ super({ ...defaults$l, ...options });
4322
4355
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4323
4356
  }
4324
4357
  static schema() {
@@ -4426,12 +4459,12 @@ function describePattern(pattern) {
4426
4459
  }
4427
4460
  }
4428
4461
 
4429
- const defaults$j = {
4462
+ const defaults$k = {
4430
4463
  pattern: "kebabcase",
4431
4464
  };
4432
4465
  class ClassPattern extends Rule {
4433
4466
  constructor(options) {
4434
- super({ ...defaults$j, ...options });
4467
+ super({ ...defaults$k, ...options });
4435
4468
  this.pattern = parsePattern(this.options.pattern);
4436
4469
  }
4437
4470
  static schema() {
@@ -4457,7 +4490,9 @@ class ClassPattern extends Rule {
4457
4490
  classes.forEach((cur, index) => {
4458
4491
  if (!cur.match(this.pattern)) {
4459
4492
  const location = classes.location(index);
4460
- this.report(event.target, `Class "${cur}" does not match required pattern "${this.pattern}"`, location);
4493
+ const pattern = this.pattern.toString();
4494
+ const message = `Class "${cur}" does not match required pattern "${pattern}"`;
4495
+ this.report(event.target, message, location);
4461
4496
  }
4462
4497
  });
4463
4498
  });
@@ -4538,13 +4573,13 @@ class CloseOrder extends Rule {
4538
4573
  }
4539
4574
  }
4540
4575
 
4541
- const defaults$i = {
4576
+ const defaults$j = {
4542
4577
  include: null,
4543
4578
  exclude: null,
4544
4579
  };
4545
4580
  class Deprecated extends Rule {
4546
4581
  constructor(options) {
4547
- super({ ...defaults$i, ...options });
4582
+ super({ ...defaults$j, ...options });
4548
4583
  }
4549
4584
  static schema() {
4550
4585
  return {
@@ -4707,12 +4742,12 @@ class NoStyleTag$1 extends Rule {
4707
4742
  }
4708
4743
  }
4709
4744
 
4710
- const defaults$h = {
4745
+ const defaults$i = {
4711
4746
  style: "uppercase",
4712
4747
  };
4713
4748
  class DoctypeStyle extends Rule {
4714
4749
  constructor(options) {
4715
- super({ ...defaults$h, ...options });
4750
+ super({ ...defaults$i, ...options });
4716
4751
  }
4717
4752
  static schema() {
4718
4753
  return {
@@ -4744,12 +4779,12 @@ class DoctypeStyle extends Rule {
4744
4779
  }
4745
4780
  }
4746
4781
 
4747
- const defaults$g = {
4782
+ const defaults$h = {
4748
4783
  style: "lowercase",
4749
4784
  };
4750
4785
  class ElementCase extends Rule {
4751
4786
  constructor(options) {
4752
- super({ ...defaults$g, ...options });
4787
+ super({ ...defaults$h, ...options });
4753
4788
  this.style = new CaseStyle(this.options.style, "element-case");
4754
4789
  }
4755
4790
  static schema() {
@@ -4773,8 +4808,11 @@ class ElementCase extends Rule {
4773
4808
  };
4774
4809
  }
4775
4810
  documentation() {
4811
+ const { style } = this.options;
4776
4812
  return {
4777
- description: `Element tagname must be ${this.options.style}.`,
4813
+ description: Array.isArray(style)
4814
+ ? [`Element tagname must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
4815
+ : `Element tagname must be in ${style}.`,
4778
4816
  url: ruleDocumentationUrl("@/rules/element-case.ts"),
4779
4817
  };
4780
4818
  }
@@ -4812,14 +4850,14 @@ class ElementCase extends Rule {
4812
4850
  }
4813
4851
  }
4814
4852
 
4815
- const defaults$f = {
4853
+ const defaults$g = {
4816
4854
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
4817
4855
  whitelist: [],
4818
4856
  blacklist: [],
4819
4857
  };
4820
4858
  class ElementName extends Rule {
4821
4859
  constructor(options) {
4822
- super({ ...defaults$f, ...options });
4860
+ super({ ...defaults$g, ...options });
4823
4861
  // eslint-disable-next-line security/detect-non-literal-regexp
4824
4862
  this.pattern = new RegExp(this.options.pattern);
4825
4863
  }
@@ -4860,7 +4898,7 @@ class ElementName extends Rule {
4860
4898
  ...context.blacklist.map((cur) => `- ${cur}`),
4861
4899
  ];
4862
4900
  }
4863
- if (context.pattern !== defaults$f.pattern) {
4901
+ if (context.pattern !== defaults$g.pattern) {
4864
4902
  return [
4865
4903
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
4866
4904
  "",
@@ -5262,7 +5300,7 @@ class EmptyTitle extends Rule {
5262
5300
  }
5263
5301
  }
5264
5302
 
5265
- const defaults$e = {
5303
+ const defaults$f = {
5266
5304
  allowMultipleH1: false,
5267
5305
  minInitialRank: "h1",
5268
5306
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5293,7 +5331,7 @@ function parseMaxInitial(value) {
5293
5331
  }
5294
5332
  class HeadingLevel extends Rule {
5295
5333
  constructor(options) {
5296
- super({ ...defaults$e, ...options });
5334
+ super({ ...defaults$f, ...options });
5297
5335
  this.stack = [];
5298
5336
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5299
5337
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5451,12 +5489,12 @@ class HeadingLevel extends Rule {
5451
5489
  }
5452
5490
  }
5453
5491
 
5454
- const defaults$d = {
5492
+ const defaults$e = {
5455
5493
  pattern: "kebabcase",
5456
5494
  };
5457
5495
  class IdPattern extends Rule {
5458
5496
  constructor(options) {
5459
- super({ ...defaults$d, ...options });
5497
+ super({ ...defaults$e, ...options });
5460
5498
  this.pattern = parsePattern(this.options.pattern);
5461
5499
  }
5462
5500
  static schema() {
@@ -5475,6 +5513,7 @@ class IdPattern extends Rule {
5475
5513
  }
5476
5514
  setup() {
5477
5515
  this.on("attr", (event) => {
5516
+ var _a;
5478
5517
  if (event.key.toLowerCase() !== "id") {
5479
5518
  return;
5480
5519
  }
@@ -5483,7 +5522,10 @@ class IdPattern extends Rule {
5483
5522
  return;
5484
5523
  }
5485
5524
  if (!event.value || !event.value.match(this.pattern)) {
5486
- this.report(event.target, `ID "${event.value}" does not match required pattern "${this.pattern}"`, event.valueLocation);
5525
+ const value = (_a = event.value) !== null && _a !== void 0 ? _a : "";
5526
+ const pattern = this.pattern.toString();
5527
+ const message = `ID "${value}" does not match required pattern "${pattern}"`;
5528
+ this.report(event.target, message, event.valueLocation);
5487
5529
  }
5488
5530
  });
5489
5531
  }
@@ -5803,12 +5845,12 @@ function findLabelByParent(el) {
5803
5845
  return [];
5804
5846
  }
5805
5847
 
5806
- const defaults$c = {
5848
+ const defaults$d = {
5807
5849
  maxlength: 70,
5808
5850
  };
5809
5851
  class LongTitle extends Rule {
5810
5852
  constructor(options) {
5811
- super({ ...defaults$c, ...options });
5853
+ super({ ...defaults$d, ...options });
5812
5854
  this.maxlength = this.options.maxlength;
5813
5855
  }
5814
5856
  static schema() {
@@ -5955,13 +5997,13 @@ class MultipleLabeledControls extends Rule {
5955
5997
  }
5956
5998
  }
5957
5999
 
5958
- const defaults$b = {
6000
+ const defaults$c = {
5959
6001
  include: null,
5960
6002
  exclude: null,
5961
6003
  };
5962
6004
  class NoAutoplay extends Rule {
5963
6005
  constructor(options) {
5964
- super({ ...defaults$b, ...options });
6006
+ super({ ...defaults$c, ...options });
5965
6007
  }
5966
6008
  documentation(context) {
5967
6009
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6202,7 +6244,7 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6202
6244
  }
6203
6245
  }
6204
6246
 
6205
- const defaults$a = {
6247
+ const defaults$b = {
6206
6248
  include: null,
6207
6249
  exclude: null,
6208
6250
  allowedProperties: ["display"],
@@ -6219,7 +6261,7 @@ function getCSSDeclarations(value) {
6219
6261
  }
6220
6262
  class NoInlineStyle extends Rule {
6221
6263
  constructor(options) {
6222
- super({ ...defaults$a, ...options });
6264
+ super({ ...defaults$b, ...options });
6223
6265
  }
6224
6266
  static schema() {
6225
6267
  return {
@@ -6424,24 +6466,24 @@ class NoMultipleMain extends Rule {
6424
6466
  }
6425
6467
  }
6426
6468
 
6427
- const defaults$9 = {
6469
+ const defaults$a = {
6428
6470
  relaxed: false,
6429
6471
  };
6430
6472
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
6431
6473
  const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
6432
6474
  const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
6433
- const replacementTable = new Map([
6434
- ['"', "&quot;"],
6435
- ["&", "&amp;"],
6436
- ["'", "&apos;"],
6437
- ["<", "&lt;"],
6438
- ["=", "&equals;"],
6439
- [">", "&gt;"],
6440
- ["`", "&grave;"],
6441
- ]);
6475
+ const replacementTable = {
6476
+ '"': "&quot;",
6477
+ "&": "&amp;",
6478
+ "'": "&apos;",
6479
+ "<": "&lt;",
6480
+ "=": "&equals;",
6481
+ ">": "&gt;",
6482
+ "`": "&grave;",
6483
+ };
6442
6484
  class NoRawCharacters extends Rule {
6443
6485
  constructor(options) {
6444
- super({ ...defaults$9, ...options });
6486
+ super({ ...defaults$a, ...options });
6445
6487
  this.relaxed = this.options.relaxed;
6446
6488
  }
6447
6489
  static schema() {
@@ -6491,7 +6533,6 @@ class NoRawCharacters extends Rule {
6491
6533
  * @param text - The full text to find unescaped raw characters in.
6492
6534
  * @param location - Location of text.
6493
6535
  * @param regexp - Regexp pattern to match using.
6494
- * @param ignore - List of characters to ignore for this text.
6495
6536
  */
6496
6537
  findRawChars(node, text, location, regexp) {
6497
6538
  let match;
@@ -6507,7 +6548,7 @@ class NoRawCharacters extends Rule {
6507
6548
  continue;
6508
6549
  }
6509
6550
  /* determine replacement character and location */
6510
- const replacement = replacementTable.get(char);
6551
+ const replacement = replacementTable[char];
6511
6552
  const charLocation = sliceLocation(location, match.index, match.index + 1);
6512
6553
  /* report as error */
6513
6554
  this.report(node, `Raw "${char}" must be encoded as "${replacement}"`, charLocation);
@@ -6620,13 +6661,13 @@ class NoRedundantRole extends Rule {
6620
6661
  }
6621
6662
 
6622
6663
  const xmlns = /^(.+):.+$/;
6623
- const defaults$8 = {
6664
+ const defaults$9 = {
6624
6665
  ignoreForeign: true,
6625
6666
  ignoreXML: true,
6626
6667
  };
6627
6668
  class NoSelfClosing extends Rule {
6628
6669
  constructor(options) {
6629
- super({ ...defaults$8, ...options });
6670
+ super({ ...defaults$9, ...options });
6630
6671
  }
6631
6672
  static schema() {
6632
6673
  return {
@@ -6759,13 +6800,13 @@ const replacement = {
6759
6800
  reset: '<button type="reset">',
6760
6801
  image: '<button type="button">',
6761
6802
  };
6762
- const defaults$7 = {
6803
+ const defaults$8 = {
6763
6804
  include: null,
6764
6805
  exclude: null,
6765
6806
  };
6766
6807
  class PreferButton extends Rule {
6767
6808
  constructor(options) {
6768
- super({ ...defaults$7, ...options });
6809
+ super({ ...defaults$8, ...options });
6769
6810
  }
6770
6811
  static schema() {
6771
6812
  return {
@@ -6840,7 +6881,7 @@ class PreferButton extends Rule {
6840
6881
  }
6841
6882
  }
6842
6883
 
6843
- const defaults$6 = {
6884
+ const defaults$7 = {
6844
6885
  mapping: {
6845
6886
  article: "article",
6846
6887
  banner: "header",
@@ -6870,7 +6911,7 @@ const defaults$6 = {
6870
6911
  };
6871
6912
  class PreferNativeElement extends Rule {
6872
6913
  constructor(options) {
6873
- super({ ...defaults$6, ...options });
6914
+ super({ ...defaults$7, ...options });
6874
6915
  }
6875
6916
  static schema() {
6876
6917
  return {
@@ -6990,7 +7031,7 @@ class PreferTbody extends Rule {
6990
7031
  }
6991
7032
  }
6992
7033
 
6993
- const defaults$5 = {
7034
+ const defaults$6 = {
6994
7035
  target: "all",
6995
7036
  };
6996
7037
  const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
@@ -7000,7 +7041,7 @@ const supportSri = {
7000
7041
  };
7001
7042
  class RequireSri extends Rule {
7002
7043
  constructor(options) {
7003
- super({ ...defaults$5, ...options });
7044
+ super({ ...defaults$6, ...options });
7004
7045
  this.target = this.options.target;
7005
7046
  }
7006
7047
  static schema() {
@@ -7128,6 +7169,155 @@ class SvgFocusable extends Rule {
7128
7169
  }
7129
7170
  }
7130
7171
 
7172
+ const defaults$5 = {
7173
+ characters: [
7174
+ { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
7175
+ { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" },
7176
+ ],
7177
+ ignoreClasses: [],
7178
+ };
7179
+ function constructRegex(characters) {
7180
+ const disallowed = characters
7181
+ .map((it) => {
7182
+ return it.pattern;
7183
+ })
7184
+ .join("|");
7185
+ const pattern = `(${disallowed})`;
7186
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7187
+ return new RegExp(pattern, "g");
7188
+ }
7189
+ function getText(node) {
7190
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
7191
+ const match = node.textContent.match(/^(\s*)(.*)$/);
7192
+ const [, leading, text] = match;
7193
+ return [leading.length, text.trimEnd()];
7194
+ }
7195
+ /**
7196
+ * Node 12 does not support String.matchAll, this simulates it's behavior.
7197
+ */
7198
+ function matchAll(text, regexp) {
7199
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7200
+ const copy = new RegExp(regexp);
7201
+ const matches = [];
7202
+ /* eslint-disable-next-line no-constant-condition */
7203
+ while (true) {
7204
+ const match = copy.exec(text);
7205
+ if (match === null) {
7206
+ break;
7207
+ }
7208
+ matches.push(match);
7209
+ }
7210
+ return matches;
7211
+ }
7212
+ class TelNonBreaking extends Rule {
7213
+ constructor(options) {
7214
+ super({ ...defaults$5, ...options });
7215
+ this.regex = constructRegex(this.options.characters);
7216
+ }
7217
+ static schema() {
7218
+ return {
7219
+ characters: {
7220
+ type: "array",
7221
+ items: {
7222
+ type: "object",
7223
+ additionalProperties: false,
7224
+ properties: {
7225
+ pattern: {
7226
+ type: "string",
7227
+ },
7228
+ replacement: {
7229
+ type: "string",
7230
+ },
7231
+ description: {
7232
+ type: "string",
7233
+ },
7234
+ },
7235
+ },
7236
+ },
7237
+ ignoreClasses: {
7238
+ type: "array",
7239
+ items: {
7240
+ type: "string",
7241
+ },
7242
+ },
7243
+ };
7244
+ }
7245
+ documentation(context) {
7246
+ const { characters } = this.options;
7247
+ const replacements = characters.map((it) => {
7248
+ return ` - \`${it.pattern}\` - replace with \`${it.replacement}\` (${it.description}).`;
7249
+ });
7250
+ return {
7251
+ description: [
7252
+ context
7253
+ ? `The \`${context.pattern}\` character should be replaced with \`${context.replacement}\` character (${context.description}) when used in a telephone number.`
7254
+ : `Replace this character with a non-breaking version.`,
7255
+ "",
7256
+ "Unless non-breaking characters is used there could be a line break inserted at that character.",
7257
+ "Line breaks make is harder to read and understand the telephone number.",
7258
+ "",
7259
+ "The following characters should be avoided:",
7260
+ "",
7261
+ ...replacements,
7262
+ ].join("\n"),
7263
+ url: ruleDocumentationUrl("@/rules/tel-non-breaking.ts"),
7264
+ };
7265
+ }
7266
+ setup() {
7267
+ this.on("element:ready", this.isRelevant, (event) => {
7268
+ const { target } = event;
7269
+ const { ignoreClasses } = this.options;
7270
+ /* skip if element has a class in the ignore list */
7271
+ const isIgnored = ignoreClasses.some((it) => target.classList.contains(it));
7272
+ if (isIgnored) {
7273
+ return;
7274
+ }
7275
+ this.walk(target);
7276
+ });
7277
+ }
7278
+ isRelevant(event) {
7279
+ const { target } = event;
7280
+ /* should only deal with anchors */
7281
+ if (!target.is("a")) {
7282
+ return false;
7283
+ }
7284
+ /* ignore if anchor does not have tel href */
7285
+ const attr = target.getAttribute("href");
7286
+ if (!attr || !attr.valueMatches(/^tel:/, false)) {
7287
+ return false;
7288
+ }
7289
+ return true;
7290
+ }
7291
+ walk(node) {
7292
+ for (const child of node.childNodes) {
7293
+ if (isTextNode(child)) {
7294
+ this.detectDisallowed(child);
7295
+ }
7296
+ else if (isElementNode(child)) {
7297
+ this.walk(child);
7298
+ }
7299
+ }
7300
+ }
7301
+ detectDisallowed(node) {
7302
+ const [offset, text] = getText(node);
7303
+ const matches = matchAll(text, this.regex);
7304
+ for (const match of matches) {
7305
+ const detected = match[0];
7306
+ const entry = this.options.characters.find((it) => it.pattern === detected);
7307
+ /* istanbul ignore next: should never happen and cannot be tested, just a sanity check */
7308
+ if (!entry) {
7309
+ throw new Error(`Failed to find entry for "${detected}" when searching text "${text}"`);
7310
+ }
7311
+ const message = `"${detected}" should be replaced with "${entry.replacement}" in telephone number`;
7312
+ const begin = offset + match.index;
7313
+ const end = begin + detected.length;
7314
+ const location = sliceLocation(node.location, begin, end);
7315
+ const context = entry;
7316
+ this.report(node, message, location, context);
7317
+ }
7318
+ }
7319
+ }
7320
+
7131
7321
  function hasAltText(image) {
7132
7322
  const alt = image.getAttribute("alt");
7133
7323
  /* missing or boolean */
@@ -9158,9 +9348,6 @@ function parseStyle$1(name) {
9158
9348
  case "selfclose":
9159
9349
  case "selfclosing":
9160
9350
  return Style$1.AlwaysSelfclose;
9161
- /* istanbul ignore next: covered by schema validation */
9162
- default:
9163
- throw new Error(`Invalid style "${name}" for "void" rule`);
9164
9351
  }
9165
9352
  }
9166
9353
 
@@ -9592,6 +9779,7 @@ const bundledRules = {
9592
9779
  "script-element": ScriptElement,
9593
9780
  "script-type": ScriptType,
9594
9781
  "svg-focusable": SvgFocusable,
9782
+ "tel-non-breaking": TelNonBreaking,
9595
9783
  "text-content": TextContent,
9596
9784
  "unrecognized-char-ref": UnknownCharReference,
9597
9785
  void: Void,
@@ -9616,7 +9804,7 @@ const config$3 = {
9616
9804
  "no-redundant-for": "error",
9617
9805
  "no-redundant-role": "error",
9618
9806
  "prefer-native-element": "error",
9619
- "svg-focusable": "error",
9807
+ "svg-focusable": "off",
9620
9808
  "text-content": "error",
9621
9809
  "wcag/h30": "error",
9622
9810
  "wcag/h32": "error",
@@ -9687,7 +9875,8 @@ const config$1 = {
9687
9875
  "prefer-tbody": "error",
9688
9876
  "script-element": "error",
9689
9877
  "script-type": "error",
9690
- "svg-focusable": "error",
9878
+ "svg-focusable": "off",
9879
+ "tel-non-breaking": "error",
9691
9880
  "text-content": "error",
9692
9881
  "unrecognized-char-ref": "error",
9693
9882
  void: "off",
@@ -9793,7 +9982,8 @@ class ResolvedConfig {
9793
9982
  });
9794
9983
  }
9795
9984
  catch (err) {
9796
- throw new NestedError(`When transforming "${source.filename}": ${err.message}`, err);
9985
+ const message = err instanceof Error ? err.message : String(err);
9986
+ throw new NestedError(`When transforming "${source.filename}": ${message}`, err);
9797
9987
  }
9798
9988
  }
9799
9989
  else {
@@ -9869,9 +10059,10 @@ function loadFromFile(filename) {
9869
10059
  }
9870
10060
  /* expand any relative paths */
9871
10061
  for (const key of ["extends", "elements", "plugins"]) {
9872
- if (!json[key])
10062
+ const value = json[key];
10063
+ if (!value)
9873
10064
  continue;
9874
- json[key] = json[key].map((ref) => {
10065
+ json[key] = value.map((ref) => {
9875
10066
  return Config.expandRelative(ref, path__default["default"].dirname(filename));
9876
10067
  });
9877
10068
  }
@@ -10029,6 +10220,7 @@ class Config {
10029
10220
  /**
10030
10221
  * Get element metadata.
10031
10222
  */
10223
+ /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10032
10224
  getMetaTable() {
10033
10225
  /* use cached table if it exists */
10034
10226
  if (this.metaTable) {
@@ -10067,7 +10259,8 @@ class Config {
10067
10259
  metaTable.loadFromObject(legacyRequire(entry));
10068
10260
  }
10069
10261
  catch (err) {
10070
- throw new ConfigError(`Failed to load elements from "${entry}": ${err.message}`, err);
10262
+ const message = err instanceof Error ? err.message : String(err);
10263
+ throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, err);
10071
10264
  }
10072
10265
  }
10073
10266
  metaTable.init();
@@ -10143,7 +10336,8 @@ class Config {
10143
10336
  return plugin;
10144
10337
  }
10145
10338
  catch (err) {
10146
- throw new ConfigError(`Failed to load plugin "${moduleName}": ${err}`, err);
10339
+ const message = err instanceof Error ? err.message : String(err);
10340
+ throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, err);
10147
10341
  }
10148
10342
  });
10149
10343
  }
@@ -10465,6 +10659,9 @@ class ParserError extends Error {
10465
10659
  function isAttrValueToken(token) {
10466
10660
  return Boolean(token && token.type === exports.TokenType.ATTR_VALUE);
10467
10661
  }
10662
+ function svgShouldRetainTag(foreignTagName, tagName) {
10663
+ return foreignTagName === "svg" && ["title", "desc"].includes(tagName);
10664
+ }
10468
10665
  /**
10469
10666
  * Parse HTML document into a DOM tree.
10470
10667
  *
@@ -10477,6 +10674,7 @@ class Parser {
10477
10674
  * @param config - Configuration
10478
10675
  */
10479
10676
  constructor(config) {
10677
+ this.currentNamespace = "";
10480
10678
  this.event = new EventHandler();
10481
10679
  this.dom = null;
10482
10680
  this.metaTable = config.getMetaTable();
@@ -10487,7 +10685,6 @@ class Parser {
10487
10685
  * @param source - HTML markup.
10488
10686
  * @returns DOM tree representing the HTML markup.
10489
10687
  */
10490
- // eslint-disable-next-line complexity
10491
10688
  parseHtml(source) {
10492
10689
  var _a, _b, _c, _d;
10493
10690
  if (typeof source === "string") {
@@ -10518,40 +10715,7 @@ class Parser {
10518
10715
  let it = this.next(tokenStream);
10519
10716
  while (!it.done) {
10520
10717
  const token = it.value;
10521
- switch (token.type) {
10522
- case exports.TokenType.UNICODE_BOM:
10523
- /* ignore */
10524
- break;
10525
- case exports.TokenType.TAG_OPEN:
10526
- this.consumeTag(source, token, tokenStream);
10527
- break;
10528
- case exports.TokenType.WHITESPACE:
10529
- this.trigger("whitespace", {
10530
- text: token.data[0],
10531
- location: token.location,
10532
- });
10533
- this.appendText(token.data[0], token.location);
10534
- break;
10535
- case exports.TokenType.DIRECTIVE:
10536
- this.consumeDirective(token);
10537
- break;
10538
- case exports.TokenType.CONDITIONAL:
10539
- this.consumeConditional(token);
10540
- break;
10541
- case exports.TokenType.COMMENT:
10542
- this.consumeComment(token);
10543
- break;
10544
- case exports.TokenType.DOCTYPE_OPEN:
10545
- this.consumeDoctype(token, tokenStream);
10546
- break;
10547
- case exports.TokenType.TEXT:
10548
- case exports.TokenType.TEMPLATING:
10549
- this.appendText(token.data[0], token.location);
10550
- break;
10551
- case exports.TokenType.EOF:
10552
- this.closeTree(source, token.location);
10553
- break;
10554
- }
10718
+ this.consume(source, token, tokenStream);
10555
10719
  it = this.next(tokenStream);
10556
10720
  }
10557
10721
  /* resolve any dynamic meta element properties */
@@ -10598,13 +10762,50 @@ class Parser {
10598
10762
  return Boolean(active.parent && active.parent.is(tagName) && meta.includes(active.tagName));
10599
10763
  }
10600
10764
  }
10765
+ /* eslint-disable-next-line complexity */
10766
+ consume(source, token, tokenStream) {
10767
+ switch (token.type) {
10768
+ case exports.TokenType.UNICODE_BOM:
10769
+ /* ignore */
10770
+ break;
10771
+ case exports.TokenType.TAG_OPEN:
10772
+ this.consumeTag(source, token, tokenStream);
10773
+ break;
10774
+ case exports.TokenType.WHITESPACE:
10775
+ this.trigger("whitespace", {
10776
+ text: token.data[0],
10777
+ location: token.location,
10778
+ });
10779
+ this.appendText(token.data[0], token.location);
10780
+ break;
10781
+ case exports.TokenType.DIRECTIVE:
10782
+ this.consumeDirective(token);
10783
+ break;
10784
+ case exports.TokenType.CONDITIONAL:
10785
+ this.consumeConditional(token);
10786
+ break;
10787
+ case exports.TokenType.COMMENT:
10788
+ this.consumeComment(token);
10789
+ break;
10790
+ case exports.TokenType.DOCTYPE_OPEN:
10791
+ this.consumeDoctype(token, tokenStream);
10792
+ break;
10793
+ case exports.TokenType.TEXT:
10794
+ case exports.TokenType.TEMPLATING:
10795
+ this.appendText(token.data[0], token.location);
10796
+ break;
10797
+ case exports.TokenType.EOF:
10798
+ this.closeTree(source, token.location);
10799
+ break;
10800
+ }
10801
+ }
10601
10802
  /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10602
10803
  consumeTag(source, startToken, tokenStream) {
10603
10804
  const tokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.TAG_CLOSE, startToken.location));
10604
10805
  const endToken = tokens.slice(-1)[0];
10605
10806
  const closeOptional = this.closeOptional(startToken);
10606
10807
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
10607
- const node = HtmlElement.fromTokens(startToken, endToken, parent, this.metaTable);
10808
+ const node = HtmlElement.fromTokens(startToken, endToken, parent, this.metaTable, this.currentNamespace);
10608
10809
  const isStartTag = !startToken.data[1];
10609
10810
  const isClosing = !isStartTag || node.closed !== exports.NodeClosed.Open;
10610
10811
  const isForeign = node.meta && node.meta.foreign;
@@ -10709,6 +10910,15 @@ class Parser {
10709
10910
  const tokens = Array.from(this.consumeUntil(tokenStream, exports.TokenType.TAG_OPEN, errorLocation));
10710
10911
  const [last] = tokens.slice(-1);
10711
10912
  const [, tagClosed, tagName] = last.data;
10913
+ /* special case: svg <title> and <desc> should be intact as it affects accessibility */
10914
+ if (!tagClosed && svgShouldRetainTag(foreignTagName, tagName)) {
10915
+ const oldNamespace = this.currentNamespace;
10916
+ this.currentNamespace = "svg";
10917
+ this.consumeTag(source, last, tokenStream);
10918
+ this.consumeUntilMatchingTag(source, tokenStream, tagName);
10919
+ this.currentNamespace = oldNamespace;
10920
+ continue;
10921
+ }
10712
10922
  /* keep going unless the new tag matches the foreign root element */
10713
10923
  if (tagName !== foreignTagName) {
10714
10924
  continue;
@@ -10907,6 +11117,35 @@ class Parser {
10907
11117
  }
10908
11118
  throw new ParserError(errorLocation, `stream ended before ${exports.TokenType[search]} token was found`);
10909
11119
  }
11120
+ /**
11121
+ * Consumes tokens until a matching close-tag is found. Tags are appended to
11122
+ * the document.
11123
+ *
11124
+ * @internal
11125
+ */
11126
+ consumeUntilMatchingTag(source, tokenStream, searchTag) {
11127
+ let numOpen = 1;
11128
+ let it = this.next(tokenStream);
11129
+ while (!it.done) {
11130
+ const token = it.value;
11131
+ this.consume(source, token, tokenStream);
11132
+ if (token.type === exports.TokenType.TAG_OPEN) {
11133
+ const [, close, tagName] = token.data;
11134
+ if (tagName === searchTag) {
11135
+ if (close) {
11136
+ numOpen--;
11137
+ }
11138
+ else {
11139
+ numOpen++;
11140
+ }
11141
+ if (numOpen === 0) {
11142
+ return;
11143
+ }
11144
+ }
11145
+ }
11146
+ it = this.next(tokenStream);
11147
+ }
11148
+ }
10910
11149
  next(tokenStream) {
10911
11150
  const it = tokenStream.next();
10912
11151
  if (!it.done) {
@@ -11102,7 +11341,7 @@ class Engine {
11102
11341
  /**
11103
11342
  * Lint sources and return report
11104
11343
  *
11105
- * @param src - Parsed source.
11344
+ * @param sources - Sources to lint.
11106
11345
  * @returns Report output.
11107
11346
  */
11108
11347
  lint(sources) {
@@ -11196,7 +11435,7 @@ class Engine {
11196
11435
  const lines = [];
11197
11436
  function decoration(node) {
11198
11437
  let output = "";
11199
- if (node.hasAttribute("id")) {
11438
+ if (node.id) {
11200
11439
  output += `#${node.id}`;
11201
11440
  }
11202
11441
  if (node.hasAttribute("class")) {