html-validate 6.6.1 → 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.
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 !== "*");
@@ -1924,7 +1951,7 @@ class HtmlElement extends DOMNode {
1924
1951
  * Similar to childNodes but only elements.
1925
1952
  */
1926
1953
  get childElements() {
1927
- return this.childNodes.filter(isElement);
1954
+ return this.childNodes.filter(isElementNode);
1928
1955
  }
1929
1956
  /**
1930
1957
  * Find the first ancestor matching a selector.
@@ -2185,8 +2212,9 @@ class HtmlElement extends DOMNode {
2185
2212
  }, []);
2186
2213
  }
2187
2214
  querySelector(selector) {
2215
+ var _a;
2188
2216
  const it = this.querySelectorImpl(selector);
2189
- 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
2190
2218
  }
2191
2219
  querySelectorAll(selector) {
2192
2220
  const it = this.querySelectorImpl(selector);
@@ -2753,8 +2781,6 @@ var configurationSchema = {
2753
2781
  };
2754
2782
 
2755
2783
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2756
- const espree = legacyRequire("espree");
2757
- const walk = legacyRequire("acorn-walk");
2758
2784
  function joinTemplateLiteral(nodes) {
2759
2785
  let offset = nodes[0].start + 1;
2760
2786
  let output = "";
@@ -2870,7 +2896,7 @@ class TemplateExtractor {
2870
2896
  }
2871
2897
  static fromFilename(filename) {
2872
2898
  const source = fs__default["default"].readFileSync(filename, "utf-8");
2873
- const ast = espree.parse(source, {
2899
+ const ast = espree__namespace.parse(source, {
2874
2900
  ecmaVersion: 2017,
2875
2901
  sourceType: "module",
2876
2902
  loc: true,
@@ -2889,7 +2915,7 @@ class TemplateExtractor {
2889
2915
  * `Source`. Defauls to `"inline"`.
2890
2916
  */
2891
2917
  static fromString(source, filename) {
2892
- const ast = espree.parse(source, {
2918
+ const ast = espree__namespace.parse(source, {
2893
2919
  ecmaVersion: 2017,
2894
2920
  sourceType: "module",
2895
2921
  loc: true,
@@ -2935,7 +2961,8 @@ class TemplateExtractor {
2935
2961
  extractObjectProperty(key) {
2936
2962
  const result = [];
2937
2963
  const { filename, data } = this;
2938
- walk.simple(this.ast, {
2964
+ const node = this.ast;
2965
+ walk__namespace.simple(node, {
2939
2966
  Property(node) {
2940
2967
  if (compareKey(node.key, key, filename)) {
2941
2968
  const source = extractLiteral(node.value, filename, data);
@@ -2959,7 +2986,7 @@ var TRANSFORMER_API;
2959
2986
  /** @public */
2960
2987
  const name = "html-validate";
2961
2988
  /** @public */
2962
- const version = "6.6.1";
2989
+ const version = "6.7.0";
2963
2990
  /** @public */
2964
2991
  const homepage = "https://html-validate.org";
2965
2992
  /** @public */
@@ -3280,7 +3307,7 @@ function ruleDocumentationUrl(filename) {
3280
3307
  return `${homepage}/rules/${normalized}.html`;
3281
3308
  }
3282
3309
 
3283
- const defaults$p = {
3310
+ const defaults$q = {
3284
3311
  allowExternal: true,
3285
3312
  allowRelative: true,
3286
3313
  allowAbsolute: true,
@@ -3324,7 +3351,7 @@ function matchList(value, list) {
3324
3351
  }
3325
3352
  class AllowedLinks extends Rule {
3326
3353
  constructor(options) {
3327
- super({ ...defaults$p, ...options });
3354
+ super({ ...defaults$q, ...options });
3328
3355
  this.allowExternal = parseAllow(this.options.allowExternal);
3329
3356
  this.allowRelative = parseAllow(this.options.allowRelative);
3330
3357
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3603,7 +3630,7 @@ class CaseStyle {
3603
3630
  default: {
3604
3631
  const last = names.slice(-1);
3605
3632
  const rest = names.slice(0, -1);
3606
- return `${rest.join(", ")} or ${last}`;
3633
+ return `${rest.join(", ")} or ${last[0]}`;
3607
3634
  }
3608
3635
  }
3609
3636
  }
@@ -3619,19 +3646,19 @@ class CaseStyle {
3619
3646
  case "camelcase":
3620
3647
  return { pattern: /^[a-z][A-Za-z]*$/, name: "camelCase" };
3621
3648
  default:
3622
- throw new ConfigError(`Invalid style "${style}" for ${ruleId} rule`);
3649
+ throw new ConfigError(`Invalid style "${cur}" for ${ruleId} rule`);
3623
3650
  }
3624
3651
  });
3625
3652
  }
3626
3653
  }
3627
3654
 
3628
- const defaults$o = {
3655
+ const defaults$p = {
3629
3656
  style: "lowercase",
3630
3657
  ignoreForeign: true,
3631
3658
  };
3632
3659
  class AttrCase extends Rule {
3633
3660
  constructor(options) {
3634
- super({ ...defaults$o, ...options });
3661
+ super({ ...defaults$p, ...options });
3635
3662
  this.style = new CaseStyle(this.options.style, "attr-case");
3636
3663
  }
3637
3664
  static schema() {
@@ -3658,8 +3685,11 @@ class AttrCase extends Rule {
3658
3685
  };
3659
3686
  }
3660
3687
  documentation() {
3688
+ const { style } = this.options;
3661
3689
  return {
3662
- 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}.`,
3663
3693
  url: ruleDocumentationUrl("@/rules/attr-case.ts"),
3664
3694
  };
3665
3695
  }
@@ -3950,7 +3980,7 @@ function isRelevant$3(event) {
3950
3980
  class AttrDelimiter extends Rule {
3951
3981
  documentation() {
3952
3982
  return {
3953
- description: `Attribute value should be separated by `,
3983
+ description: `Attribute value must not be separated by whitespace.`,
3954
3984
  url: ruleDocumentationUrl("@/rules/attr-delimiter.ts"),
3955
3985
  };
3956
3986
  }
@@ -3967,7 +3997,7 @@ class AttrDelimiter extends Rule {
3967
3997
  }
3968
3998
 
3969
3999
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
3970
- const defaults$n = {
4000
+ const defaults$o = {
3971
4001
  pattern: DEFAULT_PATTERN,
3972
4002
  ignoreForeign: true,
3973
4003
  };
@@ -4004,7 +4034,7 @@ function generateDescription(name, pattern) {
4004
4034
  }
4005
4035
  class AttrPattern extends Rule {
4006
4036
  constructor(options) {
4007
- super({ ...defaults$n, ...options });
4037
+ super({ ...defaults$o, ...options });
4008
4038
  this.pattern = generateRegexp(this.options.pattern);
4009
4039
  }
4010
4040
  static schema() {
@@ -4064,13 +4094,13 @@ var QuoteStyle;
4064
4094
  QuoteStyle["DOUBLE_QUOTE"] = "\"";
4065
4095
  QuoteStyle["AUTO_QUOTE"] = "auto";
4066
4096
  })(QuoteStyle || (QuoteStyle = {}));
4067
- const defaults$m = {
4097
+ const defaults$n = {
4068
4098
  style: "auto",
4069
4099
  unquoted: false,
4070
4100
  };
4071
4101
  class AttrQuotes extends Rule {
4072
4102
  constructor(options) {
4073
- super({ ...defaults$m, ...options });
4103
+ super({ ...defaults$n, ...options });
4074
4104
  this.style = parseStyle$4(this.options.style);
4075
4105
  }
4076
4106
  static schema() {
@@ -4235,12 +4265,12 @@ class AttributeAllowedValues extends Rule {
4235
4265
  }
4236
4266
  }
4237
4267
 
4238
- const defaults$l = {
4268
+ const defaults$m = {
4239
4269
  style: "omit",
4240
4270
  };
4241
4271
  class AttributeBooleanStyle extends Rule {
4242
4272
  constructor(options) {
4243
- super({ ...defaults$l, ...options });
4273
+ super({ ...defaults$m, ...options });
4244
4274
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4245
4275
  }
4246
4276
  static schema() {
@@ -4316,12 +4346,12 @@ function reportMessage$1(attr, style) {
4316
4346
  return "";
4317
4347
  }
4318
4348
 
4319
- const defaults$k = {
4349
+ const defaults$l = {
4320
4350
  style: "omit",
4321
4351
  };
4322
4352
  class AttributeEmptyStyle extends Rule {
4323
4353
  constructor(options) {
4324
- super({ ...defaults$k, ...options });
4354
+ super({ ...defaults$l, ...options });
4325
4355
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4326
4356
  }
4327
4357
  static schema() {
@@ -4429,12 +4459,12 @@ function describePattern(pattern) {
4429
4459
  }
4430
4460
  }
4431
4461
 
4432
- const defaults$j = {
4462
+ const defaults$k = {
4433
4463
  pattern: "kebabcase",
4434
4464
  };
4435
4465
  class ClassPattern extends Rule {
4436
4466
  constructor(options) {
4437
- super({ ...defaults$j, ...options });
4467
+ super({ ...defaults$k, ...options });
4438
4468
  this.pattern = parsePattern(this.options.pattern);
4439
4469
  }
4440
4470
  static schema() {
@@ -4460,7 +4490,9 @@ class ClassPattern extends Rule {
4460
4490
  classes.forEach((cur, index) => {
4461
4491
  if (!cur.match(this.pattern)) {
4462
4492
  const location = classes.location(index);
4463
- 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);
4464
4496
  }
4465
4497
  });
4466
4498
  });
@@ -4541,13 +4573,13 @@ class CloseOrder extends Rule {
4541
4573
  }
4542
4574
  }
4543
4575
 
4544
- const defaults$i = {
4576
+ const defaults$j = {
4545
4577
  include: null,
4546
4578
  exclude: null,
4547
4579
  };
4548
4580
  class Deprecated extends Rule {
4549
4581
  constructor(options) {
4550
- super({ ...defaults$i, ...options });
4582
+ super({ ...defaults$j, ...options });
4551
4583
  }
4552
4584
  static schema() {
4553
4585
  return {
@@ -4710,12 +4742,12 @@ class NoStyleTag$1 extends Rule {
4710
4742
  }
4711
4743
  }
4712
4744
 
4713
- const defaults$h = {
4745
+ const defaults$i = {
4714
4746
  style: "uppercase",
4715
4747
  };
4716
4748
  class DoctypeStyle extends Rule {
4717
4749
  constructor(options) {
4718
- super({ ...defaults$h, ...options });
4750
+ super({ ...defaults$i, ...options });
4719
4751
  }
4720
4752
  static schema() {
4721
4753
  return {
@@ -4747,12 +4779,12 @@ class DoctypeStyle extends Rule {
4747
4779
  }
4748
4780
  }
4749
4781
 
4750
- const defaults$g = {
4782
+ const defaults$h = {
4751
4783
  style: "lowercase",
4752
4784
  };
4753
4785
  class ElementCase extends Rule {
4754
4786
  constructor(options) {
4755
- super({ ...defaults$g, ...options });
4787
+ super({ ...defaults$h, ...options });
4756
4788
  this.style = new CaseStyle(this.options.style, "element-case");
4757
4789
  }
4758
4790
  static schema() {
@@ -4776,8 +4808,11 @@ class ElementCase extends Rule {
4776
4808
  };
4777
4809
  }
4778
4810
  documentation() {
4811
+ const { style } = this.options;
4779
4812
  return {
4780
- 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}.`,
4781
4816
  url: ruleDocumentationUrl("@/rules/element-case.ts"),
4782
4817
  };
4783
4818
  }
@@ -4815,14 +4850,14 @@ class ElementCase extends Rule {
4815
4850
  }
4816
4851
  }
4817
4852
 
4818
- const defaults$f = {
4853
+ const defaults$g = {
4819
4854
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
4820
4855
  whitelist: [],
4821
4856
  blacklist: [],
4822
4857
  };
4823
4858
  class ElementName extends Rule {
4824
4859
  constructor(options) {
4825
- super({ ...defaults$f, ...options });
4860
+ super({ ...defaults$g, ...options });
4826
4861
  // eslint-disable-next-line security/detect-non-literal-regexp
4827
4862
  this.pattern = new RegExp(this.options.pattern);
4828
4863
  }
@@ -4863,7 +4898,7 @@ class ElementName extends Rule {
4863
4898
  ...context.blacklist.map((cur) => `- ${cur}`),
4864
4899
  ];
4865
4900
  }
4866
- if (context.pattern !== defaults$f.pattern) {
4901
+ if (context.pattern !== defaults$g.pattern) {
4867
4902
  return [
4868
4903
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
4869
4904
  "",
@@ -5265,7 +5300,7 @@ class EmptyTitle extends Rule {
5265
5300
  }
5266
5301
  }
5267
5302
 
5268
- const defaults$e = {
5303
+ const defaults$f = {
5269
5304
  allowMultipleH1: false,
5270
5305
  minInitialRank: "h1",
5271
5306
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5296,7 +5331,7 @@ function parseMaxInitial(value) {
5296
5331
  }
5297
5332
  class HeadingLevel extends Rule {
5298
5333
  constructor(options) {
5299
- super({ ...defaults$e, ...options });
5334
+ super({ ...defaults$f, ...options });
5300
5335
  this.stack = [];
5301
5336
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5302
5337
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5454,12 +5489,12 @@ class HeadingLevel extends Rule {
5454
5489
  }
5455
5490
  }
5456
5491
 
5457
- const defaults$d = {
5492
+ const defaults$e = {
5458
5493
  pattern: "kebabcase",
5459
5494
  };
5460
5495
  class IdPattern extends Rule {
5461
5496
  constructor(options) {
5462
- super({ ...defaults$d, ...options });
5497
+ super({ ...defaults$e, ...options });
5463
5498
  this.pattern = parsePattern(this.options.pattern);
5464
5499
  }
5465
5500
  static schema() {
@@ -5478,6 +5513,7 @@ class IdPattern extends Rule {
5478
5513
  }
5479
5514
  setup() {
5480
5515
  this.on("attr", (event) => {
5516
+ var _a;
5481
5517
  if (event.key.toLowerCase() !== "id") {
5482
5518
  return;
5483
5519
  }
@@ -5486,7 +5522,10 @@ class IdPattern extends Rule {
5486
5522
  return;
5487
5523
  }
5488
5524
  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);
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);
5490
5529
  }
5491
5530
  });
5492
5531
  }
@@ -5806,12 +5845,12 @@ function findLabelByParent(el) {
5806
5845
  return [];
5807
5846
  }
5808
5847
 
5809
- const defaults$c = {
5848
+ const defaults$d = {
5810
5849
  maxlength: 70,
5811
5850
  };
5812
5851
  class LongTitle extends Rule {
5813
5852
  constructor(options) {
5814
- super({ ...defaults$c, ...options });
5853
+ super({ ...defaults$d, ...options });
5815
5854
  this.maxlength = this.options.maxlength;
5816
5855
  }
5817
5856
  static schema() {
@@ -5958,13 +5997,13 @@ class MultipleLabeledControls extends Rule {
5958
5997
  }
5959
5998
  }
5960
5999
 
5961
- const defaults$b = {
6000
+ const defaults$c = {
5962
6001
  include: null,
5963
6002
  exclude: null,
5964
6003
  };
5965
6004
  class NoAutoplay extends Rule {
5966
6005
  constructor(options) {
5967
- super({ ...defaults$b, ...options });
6006
+ super({ ...defaults$c, ...options });
5968
6007
  }
5969
6008
  documentation(context) {
5970
6009
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6205,7 +6244,7 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6205
6244
  }
6206
6245
  }
6207
6246
 
6208
- const defaults$a = {
6247
+ const defaults$b = {
6209
6248
  include: null,
6210
6249
  exclude: null,
6211
6250
  allowedProperties: ["display"],
@@ -6222,7 +6261,7 @@ function getCSSDeclarations(value) {
6222
6261
  }
6223
6262
  class NoInlineStyle extends Rule {
6224
6263
  constructor(options) {
6225
- super({ ...defaults$a, ...options });
6264
+ super({ ...defaults$b, ...options });
6226
6265
  }
6227
6266
  static schema() {
6228
6267
  return {
@@ -6427,24 +6466,24 @@ class NoMultipleMain extends Rule {
6427
6466
  }
6428
6467
  }
6429
6468
 
6430
- const defaults$9 = {
6469
+ const defaults$a = {
6431
6470
  relaxed: false,
6432
6471
  };
6433
6472
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
6434
6473
  const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
6435
6474
  const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
6436
- const replacementTable = new Map([
6437
- ['"', "&quot;"],
6438
- ["&", "&amp;"],
6439
- ["'", "&apos;"],
6440
- ["<", "&lt;"],
6441
- ["=", "&equals;"],
6442
- [">", "&gt;"],
6443
- ["`", "&grave;"],
6444
- ]);
6475
+ const replacementTable = {
6476
+ '"': "&quot;",
6477
+ "&": "&amp;",
6478
+ "'": "&apos;",
6479
+ "<": "&lt;",
6480
+ "=": "&equals;",
6481
+ ">": "&gt;",
6482
+ "`": "&grave;",
6483
+ };
6445
6484
  class NoRawCharacters extends Rule {
6446
6485
  constructor(options) {
6447
- super({ ...defaults$9, ...options });
6486
+ super({ ...defaults$a, ...options });
6448
6487
  this.relaxed = this.options.relaxed;
6449
6488
  }
6450
6489
  static schema() {
@@ -6494,7 +6533,6 @@ class NoRawCharacters extends Rule {
6494
6533
  * @param text - The full text to find unescaped raw characters in.
6495
6534
  * @param location - Location of text.
6496
6535
  * @param regexp - Regexp pattern to match using.
6497
- * @param ignore - List of characters to ignore for this text.
6498
6536
  */
6499
6537
  findRawChars(node, text, location, regexp) {
6500
6538
  let match;
@@ -6510,7 +6548,7 @@ class NoRawCharacters extends Rule {
6510
6548
  continue;
6511
6549
  }
6512
6550
  /* determine replacement character and location */
6513
- const replacement = replacementTable.get(char);
6551
+ const replacement = replacementTable[char];
6514
6552
  const charLocation = sliceLocation(location, match.index, match.index + 1);
6515
6553
  /* report as error */
6516
6554
  this.report(node, `Raw "${char}" must be encoded as "${replacement}"`, charLocation);
@@ -6623,13 +6661,13 @@ class NoRedundantRole extends Rule {
6623
6661
  }
6624
6662
 
6625
6663
  const xmlns = /^(.+):.+$/;
6626
- const defaults$8 = {
6664
+ const defaults$9 = {
6627
6665
  ignoreForeign: true,
6628
6666
  ignoreXML: true,
6629
6667
  };
6630
6668
  class NoSelfClosing extends Rule {
6631
6669
  constructor(options) {
6632
- super({ ...defaults$8, ...options });
6670
+ super({ ...defaults$9, ...options });
6633
6671
  }
6634
6672
  static schema() {
6635
6673
  return {
@@ -6762,13 +6800,13 @@ const replacement = {
6762
6800
  reset: '<button type="reset">',
6763
6801
  image: '<button type="button">',
6764
6802
  };
6765
- const defaults$7 = {
6803
+ const defaults$8 = {
6766
6804
  include: null,
6767
6805
  exclude: null,
6768
6806
  };
6769
6807
  class PreferButton extends Rule {
6770
6808
  constructor(options) {
6771
- super({ ...defaults$7, ...options });
6809
+ super({ ...defaults$8, ...options });
6772
6810
  }
6773
6811
  static schema() {
6774
6812
  return {
@@ -6843,7 +6881,7 @@ class PreferButton extends Rule {
6843
6881
  }
6844
6882
  }
6845
6883
 
6846
- const defaults$6 = {
6884
+ const defaults$7 = {
6847
6885
  mapping: {
6848
6886
  article: "article",
6849
6887
  banner: "header",
@@ -6873,7 +6911,7 @@ const defaults$6 = {
6873
6911
  };
6874
6912
  class PreferNativeElement extends Rule {
6875
6913
  constructor(options) {
6876
- super({ ...defaults$6, ...options });
6914
+ super({ ...defaults$7, ...options });
6877
6915
  }
6878
6916
  static schema() {
6879
6917
  return {
@@ -6993,7 +7031,7 @@ class PreferTbody extends Rule {
6993
7031
  }
6994
7032
  }
6995
7033
 
6996
- const defaults$5 = {
7034
+ const defaults$6 = {
6997
7035
  target: "all",
6998
7036
  };
6999
7037
  const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
@@ -7003,7 +7041,7 @@ const supportSri = {
7003
7041
  };
7004
7042
  class RequireSri extends Rule {
7005
7043
  constructor(options) {
7006
- super({ ...defaults$5, ...options });
7044
+ super({ ...defaults$6, ...options });
7007
7045
  this.target = this.options.target;
7008
7046
  }
7009
7047
  static schema() {
@@ -7131,6 +7169,155 @@ class SvgFocusable extends Rule {
7131
7169
  }
7132
7170
  }
7133
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
+
7134
7321
  function hasAltText(image) {
7135
7322
  const alt = image.getAttribute("alt");
7136
7323
  /* missing or boolean */
@@ -9161,9 +9348,6 @@ function parseStyle$1(name) {
9161
9348
  case "selfclose":
9162
9349
  case "selfclosing":
9163
9350
  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
9351
  }
9168
9352
  }
9169
9353
 
@@ -9595,6 +9779,7 @@ const bundledRules = {
9595
9779
  "script-element": ScriptElement,
9596
9780
  "script-type": ScriptType,
9597
9781
  "svg-focusable": SvgFocusable,
9782
+ "tel-non-breaking": TelNonBreaking,
9598
9783
  "text-content": TextContent,
9599
9784
  "unrecognized-char-ref": UnknownCharReference,
9600
9785
  void: Void,
@@ -9691,6 +9876,7 @@ const config$1 = {
9691
9876
  "script-element": "error",
9692
9877
  "script-type": "error",
9693
9878
  "svg-focusable": "off",
9879
+ "tel-non-breaking": "error",
9694
9880
  "text-content": "error",
9695
9881
  "unrecognized-char-ref": "error",
9696
9882
  void: "off",
@@ -9796,7 +9982,8 @@ class ResolvedConfig {
9796
9982
  });
9797
9983
  }
9798
9984
  catch (err) {
9799
- 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);
9800
9987
  }
9801
9988
  }
9802
9989
  else {
@@ -9872,9 +10059,10 @@ function loadFromFile(filename) {
9872
10059
  }
9873
10060
  /* expand any relative paths */
9874
10061
  for (const key of ["extends", "elements", "plugins"]) {
9875
- if (!json[key])
10062
+ const value = json[key];
10063
+ if (!value)
9876
10064
  continue;
9877
- json[key] = json[key].map((ref) => {
10065
+ json[key] = value.map((ref) => {
9878
10066
  return Config.expandRelative(ref, path__default["default"].dirname(filename));
9879
10067
  });
9880
10068
  }
@@ -10032,6 +10220,7 @@ class Config {
10032
10220
  /**
10033
10221
  * Get element metadata.
10034
10222
  */
10223
+ /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10035
10224
  getMetaTable() {
10036
10225
  /* use cached table if it exists */
10037
10226
  if (this.metaTable) {
@@ -10070,7 +10259,8 @@ class Config {
10070
10259
  metaTable.loadFromObject(legacyRequire(entry));
10071
10260
  }
10072
10261
  catch (err) {
10073
- 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);
10074
10264
  }
10075
10265
  }
10076
10266
  metaTable.init();
@@ -10146,7 +10336,8 @@ class Config {
10146
10336
  return plugin;
10147
10337
  }
10148
10338
  catch (err) {
10149
- 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);
10150
10341
  }
10151
10342
  });
10152
10343
  }
@@ -11150,7 +11341,7 @@ class Engine {
11150
11341
  /**
11151
11342
  * Lint sources and return report
11152
11343
  *
11153
- * @param src - Parsed source.
11344
+ * @param sources - Sources to lint.
11154
11345
  * @returns Report output.
11155
11346
  */
11156
11347
  lint(sources) {
@@ -11244,7 +11435,7 @@ class Engine {
11244
11435
  const lines = [];
11245
11436
  function decoration(node) {
11246
11437
  let output = "";
11247
- if (node.hasAttribute("id")) {
11438
+ if (node.id) {
11248
11439
  output += `#${node.id}`;
11249
11440
  }
11250
11441
  if (node.hasAttribute("class")) {