html-validate 6.6.0 → 6.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/core.js CHANGED
@@ -2,6 +2,8 @@ import fs from 'fs';
2
2
  import betterAjvErrors from '@sidvind/better-ajv-errors';
3
3
  import Ajv from 'ajv';
4
4
  import deepmerge from 'deepmerge';
5
+ import * as espree from 'espree';
6
+ import * as walk from 'acorn-walk';
5
7
  import path from 'path';
6
8
  import semver from 'semver';
7
9
  import kleur from 'kleur';
@@ -245,7 +247,7 @@ class NestedError extends Error {
245
247
  constructor(message, nested) {
246
248
  super(message);
247
249
  Error.captureStackTrace(this, NestedError);
248
- if (nested) {
250
+ if (nested && nested.stack) {
249
251
  this.stack += `\nCaused by: ${nested.stack}`;
250
252
  }
251
253
  }
@@ -1838,8 +1840,13 @@ var NodeClosed;
1838
1840
  NodeClosed[NodeClosed["VoidSelfClosed"] = 3] = "VoidSelfClosed";
1839
1841
  NodeClosed[NodeClosed["ImplicitClosed"] = 4] = "ImplicitClosed";
1840
1842
  })(NodeClosed || (NodeClosed = {}));
1841
- function isElement(node) {
1842
- return node.nodeType === NodeType.ELEMENT_NODE;
1843
+ /**
1844
+ * Returns true if the node is an element node.
1845
+ *
1846
+ * @public
1847
+ */
1848
+ function isElementNode(node) {
1849
+ return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
1843
1850
  }
1844
1851
  function isValidTagName(tagName) {
1845
1852
  return Boolean(tagName !== "" && tagName !== "*");
@@ -1913,7 +1920,7 @@ class HtmlElement extends DOMNode {
1913
1920
  * Similar to childNodes but only elements.
1914
1921
  */
1915
1922
  get childElements() {
1916
- return this.childNodes.filter(isElement);
1923
+ return this.childNodes.filter(isElementNode);
1917
1924
  }
1918
1925
  /**
1919
1926
  * Find the first ancestor matching a selector.
@@ -2174,8 +2181,9 @@ class HtmlElement extends DOMNode {
2174
2181
  }, []);
2175
2182
  }
2176
2183
  querySelector(selector) {
2184
+ var _a;
2177
2185
  const it = this.querySelectorImpl(selector);
2178
- return it.next().value || null;
2186
+ return (_a = it.next().value) !== null && _a !== void 0 ? _a : null; // eslint-disable-line @typescript-eslint/no-unsafe-return
2179
2187
  }
2180
2188
  querySelectorAll(selector) {
2181
2189
  const it = this.querySelectorImpl(selector);
@@ -2742,8 +2750,6 @@ var configurationSchema = {
2742
2750
  };
2743
2751
 
2744
2752
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2745
- const espree = legacyRequire("espree");
2746
- const walk = legacyRequire("acorn-walk");
2747
2753
  function joinTemplateLiteral(nodes) {
2748
2754
  let offset = nodes[0].start + 1;
2749
2755
  let output = "";
@@ -2924,7 +2930,8 @@ class TemplateExtractor {
2924
2930
  extractObjectProperty(key) {
2925
2931
  const result = [];
2926
2932
  const { filename, data } = this;
2927
- walk.simple(this.ast, {
2933
+ const node = this.ast;
2934
+ walk.simple(node, {
2928
2935
  Property(node) {
2929
2936
  if (compareKey(node.key, key, filename)) {
2930
2937
  const source = extractLiteral(node.value, filename, data);
@@ -2948,7 +2955,7 @@ var TRANSFORMER_API;
2948
2955
  /** @public */
2949
2956
  const name = "html-validate";
2950
2957
  /** @public */
2951
- const version = "6.6.0";
2958
+ const version = "6.7.1";
2952
2959
  /** @public */
2953
2960
  const homepage = "https://html-validate.org";
2954
2961
  /** @public */
@@ -3269,7 +3276,7 @@ function ruleDocumentationUrl(filename) {
3269
3276
  return `${homepage}/rules/${normalized}.html`;
3270
3277
  }
3271
3278
 
3272
- const defaults$p = {
3279
+ const defaults$q = {
3273
3280
  allowExternal: true,
3274
3281
  allowRelative: true,
3275
3282
  allowAbsolute: true,
@@ -3313,7 +3320,7 @@ function matchList(value, list) {
3313
3320
  }
3314
3321
  class AllowedLinks extends Rule {
3315
3322
  constructor(options) {
3316
- super({ ...defaults$p, ...options });
3323
+ super({ ...defaults$q, ...options });
3317
3324
  this.allowExternal = parseAllow(this.options.allowExternal);
3318
3325
  this.allowRelative = parseAllow(this.options.allowRelative);
3319
3326
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3592,7 +3599,7 @@ class CaseStyle {
3592
3599
  default: {
3593
3600
  const last = names.slice(-1);
3594
3601
  const rest = names.slice(0, -1);
3595
- return `${rest.join(", ")} or ${last}`;
3602
+ return `${rest.join(", ")} or ${last[0]}`;
3596
3603
  }
3597
3604
  }
3598
3605
  }
@@ -3608,19 +3615,19 @@ class CaseStyle {
3608
3615
  case "camelcase":
3609
3616
  return { pattern: /^[a-z][A-Za-z]*$/, name: "camelCase" };
3610
3617
  default:
3611
- throw new ConfigError(`Invalid style "${style}" for ${ruleId} rule`);
3618
+ throw new ConfigError(`Invalid style "${cur}" for ${ruleId} rule`);
3612
3619
  }
3613
3620
  });
3614
3621
  }
3615
3622
  }
3616
3623
 
3617
- const defaults$o = {
3624
+ const defaults$p = {
3618
3625
  style: "lowercase",
3619
3626
  ignoreForeign: true,
3620
3627
  };
3621
3628
  class AttrCase extends Rule {
3622
3629
  constructor(options) {
3623
- super({ ...defaults$o, ...options });
3630
+ super({ ...defaults$p, ...options });
3624
3631
  this.style = new CaseStyle(this.options.style, "attr-case");
3625
3632
  }
3626
3633
  static schema() {
@@ -3647,8 +3654,11 @@ class AttrCase extends Rule {
3647
3654
  };
3648
3655
  }
3649
3656
  documentation() {
3657
+ const { style } = this.options;
3650
3658
  return {
3651
- description: `Attribute name must be ${this.options.style}.`,
3659
+ description: Array.isArray(style)
3660
+ ? [`Attribute name must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
3661
+ : `Attribute name must be in ${style}.`,
3652
3662
  url: ruleDocumentationUrl("@/rules/attr-case.ts"),
3653
3663
  };
3654
3664
  }
@@ -3939,7 +3949,7 @@ function isRelevant$3(event) {
3939
3949
  class AttrDelimiter extends Rule {
3940
3950
  documentation() {
3941
3951
  return {
3942
- description: `Attribute value should be separated by `,
3952
+ description: `Attribute value must not be separated by whitespace.`,
3943
3953
  url: ruleDocumentationUrl("@/rules/attr-delimiter.ts"),
3944
3954
  };
3945
3955
  }
@@ -3956,7 +3966,7 @@ class AttrDelimiter extends Rule {
3956
3966
  }
3957
3967
 
3958
3968
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
3959
- const defaults$n = {
3969
+ const defaults$o = {
3960
3970
  pattern: DEFAULT_PATTERN,
3961
3971
  ignoreForeign: true,
3962
3972
  };
@@ -3993,7 +4003,7 @@ function generateDescription(name, pattern) {
3993
4003
  }
3994
4004
  class AttrPattern extends Rule {
3995
4005
  constructor(options) {
3996
- super({ ...defaults$n, ...options });
4006
+ super({ ...defaults$o, ...options });
3997
4007
  this.pattern = generateRegexp(this.options.pattern);
3998
4008
  }
3999
4009
  static schema() {
@@ -4053,13 +4063,13 @@ var QuoteStyle;
4053
4063
  QuoteStyle["DOUBLE_QUOTE"] = "\"";
4054
4064
  QuoteStyle["AUTO_QUOTE"] = "auto";
4055
4065
  })(QuoteStyle || (QuoteStyle = {}));
4056
- const defaults$m = {
4066
+ const defaults$n = {
4057
4067
  style: "auto",
4058
4068
  unquoted: false,
4059
4069
  };
4060
4070
  class AttrQuotes extends Rule {
4061
4071
  constructor(options) {
4062
- super({ ...defaults$m, ...options });
4072
+ super({ ...defaults$n, ...options });
4063
4073
  this.style = parseStyle$4(this.options.style);
4064
4074
  }
4065
4075
  static schema() {
@@ -4224,12 +4234,12 @@ class AttributeAllowedValues extends Rule {
4224
4234
  }
4225
4235
  }
4226
4236
 
4227
- const defaults$l = {
4237
+ const defaults$m = {
4228
4238
  style: "omit",
4229
4239
  };
4230
4240
  class AttributeBooleanStyle extends Rule {
4231
4241
  constructor(options) {
4232
- super({ ...defaults$l, ...options });
4242
+ super({ ...defaults$m, ...options });
4233
4243
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4234
4244
  }
4235
4245
  static schema() {
@@ -4305,12 +4315,12 @@ function reportMessage$1(attr, style) {
4305
4315
  return "";
4306
4316
  }
4307
4317
 
4308
- const defaults$k = {
4318
+ const defaults$l = {
4309
4319
  style: "omit",
4310
4320
  };
4311
4321
  class AttributeEmptyStyle extends Rule {
4312
4322
  constructor(options) {
4313
- super({ ...defaults$k, ...options });
4323
+ super({ ...defaults$l, ...options });
4314
4324
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4315
4325
  }
4316
4326
  static schema() {
@@ -4418,12 +4428,12 @@ function describePattern(pattern) {
4418
4428
  }
4419
4429
  }
4420
4430
 
4421
- const defaults$j = {
4431
+ const defaults$k = {
4422
4432
  pattern: "kebabcase",
4423
4433
  };
4424
4434
  class ClassPattern extends Rule {
4425
4435
  constructor(options) {
4426
- super({ ...defaults$j, ...options });
4436
+ super({ ...defaults$k, ...options });
4427
4437
  this.pattern = parsePattern(this.options.pattern);
4428
4438
  }
4429
4439
  static schema() {
@@ -4449,7 +4459,9 @@ class ClassPattern extends Rule {
4449
4459
  classes.forEach((cur, index) => {
4450
4460
  if (!cur.match(this.pattern)) {
4451
4461
  const location = classes.location(index);
4452
- this.report(event.target, `Class "${cur}" does not match required pattern "${this.pattern}"`, location);
4462
+ const pattern = this.pattern.toString();
4463
+ const message = `Class "${cur}" does not match required pattern "${pattern}"`;
4464
+ this.report(event.target, message, location);
4453
4465
  }
4454
4466
  });
4455
4467
  });
@@ -4530,13 +4542,13 @@ class CloseOrder extends Rule {
4530
4542
  }
4531
4543
  }
4532
4544
 
4533
- const defaults$i = {
4545
+ const defaults$j = {
4534
4546
  include: null,
4535
4547
  exclude: null,
4536
4548
  };
4537
4549
  class Deprecated extends Rule {
4538
4550
  constructor(options) {
4539
- super({ ...defaults$i, ...options });
4551
+ super({ ...defaults$j, ...options });
4540
4552
  }
4541
4553
  static schema() {
4542
4554
  return {
@@ -4699,12 +4711,12 @@ class NoStyleTag$1 extends Rule {
4699
4711
  }
4700
4712
  }
4701
4713
 
4702
- const defaults$h = {
4714
+ const defaults$i = {
4703
4715
  style: "uppercase",
4704
4716
  };
4705
4717
  class DoctypeStyle extends Rule {
4706
4718
  constructor(options) {
4707
- super({ ...defaults$h, ...options });
4719
+ super({ ...defaults$i, ...options });
4708
4720
  }
4709
4721
  static schema() {
4710
4722
  return {
@@ -4736,12 +4748,12 @@ class DoctypeStyle extends Rule {
4736
4748
  }
4737
4749
  }
4738
4750
 
4739
- const defaults$g = {
4751
+ const defaults$h = {
4740
4752
  style: "lowercase",
4741
4753
  };
4742
4754
  class ElementCase extends Rule {
4743
4755
  constructor(options) {
4744
- super({ ...defaults$g, ...options });
4756
+ super({ ...defaults$h, ...options });
4745
4757
  this.style = new CaseStyle(this.options.style, "element-case");
4746
4758
  }
4747
4759
  static schema() {
@@ -4765,8 +4777,11 @@ class ElementCase extends Rule {
4765
4777
  };
4766
4778
  }
4767
4779
  documentation() {
4780
+ const { style } = this.options;
4768
4781
  return {
4769
- description: `Element tagname must be ${this.options.style}.`,
4782
+ description: Array.isArray(style)
4783
+ ? [`Element tagname must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
4784
+ : `Element tagname must be in ${style}.`,
4770
4785
  url: ruleDocumentationUrl("@/rules/element-case.ts"),
4771
4786
  };
4772
4787
  }
@@ -4804,14 +4819,14 @@ class ElementCase extends Rule {
4804
4819
  }
4805
4820
  }
4806
4821
 
4807
- const defaults$f = {
4822
+ const defaults$g = {
4808
4823
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
4809
4824
  whitelist: [],
4810
4825
  blacklist: [],
4811
4826
  };
4812
4827
  class ElementName extends Rule {
4813
4828
  constructor(options) {
4814
- super({ ...defaults$f, ...options });
4829
+ super({ ...defaults$g, ...options });
4815
4830
  // eslint-disable-next-line security/detect-non-literal-regexp
4816
4831
  this.pattern = new RegExp(this.options.pattern);
4817
4832
  }
@@ -4852,7 +4867,7 @@ class ElementName extends Rule {
4852
4867
  ...context.blacklist.map((cur) => `- ${cur}`),
4853
4868
  ];
4854
4869
  }
4855
- if (context.pattern !== defaults$f.pattern) {
4870
+ if (context.pattern !== defaults$g.pattern) {
4856
4871
  return [
4857
4872
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
4858
4873
  "",
@@ -5254,7 +5269,7 @@ class EmptyTitle extends Rule {
5254
5269
  }
5255
5270
  }
5256
5271
 
5257
- const defaults$e = {
5272
+ const defaults$f = {
5258
5273
  allowMultipleH1: false,
5259
5274
  minInitialRank: "h1",
5260
5275
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5285,7 +5300,7 @@ function parseMaxInitial(value) {
5285
5300
  }
5286
5301
  class HeadingLevel extends Rule {
5287
5302
  constructor(options) {
5288
- super({ ...defaults$e, ...options });
5303
+ super({ ...defaults$f, ...options });
5289
5304
  this.stack = [];
5290
5305
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5291
5306
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5443,12 +5458,12 @@ class HeadingLevel extends Rule {
5443
5458
  }
5444
5459
  }
5445
5460
 
5446
- const defaults$d = {
5461
+ const defaults$e = {
5447
5462
  pattern: "kebabcase",
5448
5463
  };
5449
5464
  class IdPattern extends Rule {
5450
5465
  constructor(options) {
5451
- super({ ...defaults$d, ...options });
5466
+ super({ ...defaults$e, ...options });
5452
5467
  this.pattern = parsePattern(this.options.pattern);
5453
5468
  }
5454
5469
  static schema() {
@@ -5467,6 +5482,7 @@ class IdPattern extends Rule {
5467
5482
  }
5468
5483
  setup() {
5469
5484
  this.on("attr", (event) => {
5485
+ var _a;
5470
5486
  if (event.key.toLowerCase() !== "id") {
5471
5487
  return;
5472
5488
  }
@@ -5475,7 +5491,10 @@ class IdPattern extends Rule {
5475
5491
  return;
5476
5492
  }
5477
5493
  if (!event.value || !event.value.match(this.pattern)) {
5478
- this.report(event.target, `ID "${event.value}" does not match required pattern "${this.pattern}"`, event.valueLocation);
5494
+ const value = (_a = event.value) !== null && _a !== void 0 ? _a : "";
5495
+ const pattern = this.pattern.toString();
5496
+ const message = `ID "${value}" does not match required pattern "${pattern}"`;
5497
+ this.report(event.target, message, event.valueLocation);
5479
5498
  }
5480
5499
  });
5481
5500
  }
@@ -5795,12 +5814,12 @@ function findLabelByParent(el) {
5795
5814
  return [];
5796
5815
  }
5797
5816
 
5798
- const defaults$c = {
5817
+ const defaults$d = {
5799
5818
  maxlength: 70,
5800
5819
  };
5801
5820
  class LongTitle extends Rule {
5802
5821
  constructor(options) {
5803
- super({ ...defaults$c, ...options });
5822
+ super({ ...defaults$d, ...options });
5804
5823
  this.maxlength = this.options.maxlength;
5805
5824
  }
5806
5825
  static schema() {
@@ -5947,13 +5966,13 @@ class MultipleLabeledControls extends Rule {
5947
5966
  }
5948
5967
  }
5949
5968
 
5950
- const defaults$b = {
5969
+ const defaults$c = {
5951
5970
  include: null,
5952
5971
  exclude: null,
5953
5972
  };
5954
5973
  class NoAutoplay extends Rule {
5955
5974
  constructor(options) {
5956
- super({ ...defaults$b, ...options });
5975
+ super({ ...defaults$c, ...options });
5957
5976
  }
5958
5977
  documentation(context) {
5959
5978
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6194,7 +6213,7 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6194
6213
  }
6195
6214
  }
6196
6215
 
6197
- const defaults$a = {
6216
+ const defaults$b = {
6198
6217
  include: null,
6199
6218
  exclude: null,
6200
6219
  allowedProperties: ["display"],
@@ -6211,7 +6230,7 @@ function getCSSDeclarations(value) {
6211
6230
  }
6212
6231
  class NoInlineStyle extends Rule {
6213
6232
  constructor(options) {
6214
- super({ ...defaults$a, ...options });
6233
+ super({ ...defaults$b, ...options });
6215
6234
  }
6216
6235
  static schema() {
6217
6236
  return {
@@ -6416,24 +6435,24 @@ class NoMultipleMain extends Rule {
6416
6435
  }
6417
6436
  }
6418
6437
 
6419
- const defaults$9 = {
6438
+ const defaults$a = {
6420
6439
  relaxed: false,
6421
6440
  };
6422
6441
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
6423
6442
  const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
6424
6443
  const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
6425
- const replacementTable = new Map([
6426
- ['"', "&quot;"],
6427
- ["&", "&amp;"],
6428
- ["'", "&apos;"],
6429
- ["<", "&lt;"],
6430
- ["=", "&equals;"],
6431
- [">", "&gt;"],
6432
- ["`", "&grave;"],
6433
- ]);
6444
+ const replacementTable = {
6445
+ '"': "&quot;",
6446
+ "&": "&amp;",
6447
+ "'": "&apos;",
6448
+ "<": "&lt;",
6449
+ "=": "&equals;",
6450
+ ">": "&gt;",
6451
+ "`": "&grave;",
6452
+ };
6434
6453
  class NoRawCharacters extends Rule {
6435
6454
  constructor(options) {
6436
- super({ ...defaults$9, ...options });
6455
+ super({ ...defaults$a, ...options });
6437
6456
  this.relaxed = this.options.relaxed;
6438
6457
  }
6439
6458
  static schema() {
@@ -6483,7 +6502,6 @@ class NoRawCharacters extends Rule {
6483
6502
  * @param text - The full text to find unescaped raw characters in.
6484
6503
  * @param location - Location of text.
6485
6504
  * @param regexp - Regexp pattern to match using.
6486
- * @param ignore - List of characters to ignore for this text.
6487
6505
  */
6488
6506
  findRawChars(node, text, location, regexp) {
6489
6507
  let match;
@@ -6499,7 +6517,7 @@ class NoRawCharacters extends Rule {
6499
6517
  continue;
6500
6518
  }
6501
6519
  /* determine replacement character and location */
6502
- const replacement = replacementTable.get(char);
6520
+ const replacement = replacementTable[char];
6503
6521
  const charLocation = sliceLocation(location, match.index, match.index + 1);
6504
6522
  /* report as error */
6505
6523
  this.report(node, `Raw "${char}" must be encoded as "${replacement}"`, charLocation);
@@ -6612,13 +6630,13 @@ class NoRedundantRole extends Rule {
6612
6630
  }
6613
6631
 
6614
6632
  const xmlns = /^(.+):.+$/;
6615
- const defaults$8 = {
6633
+ const defaults$9 = {
6616
6634
  ignoreForeign: true,
6617
6635
  ignoreXML: true,
6618
6636
  };
6619
6637
  class NoSelfClosing extends Rule {
6620
6638
  constructor(options) {
6621
- super({ ...defaults$8, ...options });
6639
+ super({ ...defaults$9, ...options });
6622
6640
  }
6623
6641
  static schema() {
6624
6642
  return {
@@ -6751,13 +6769,13 @@ const replacement = {
6751
6769
  reset: '<button type="reset">',
6752
6770
  image: '<button type="button">',
6753
6771
  };
6754
- const defaults$7 = {
6772
+ const defaults$8 = {
6755
6773
  include: null,
6756
6774
  exclude: null,
6757
6775
  };
6758
6776
  class PreferButton extends Rule {
6759
6777
  constructor(options) {
6760
- super({ ...defaults$7, ...options });
6778
+ super({ ...defaults$8, ...options });
6761
6779
  }
6762
6780
  static schema() {
6763
6781
  return {
@@ -6832,7 +6850,7 @@ class PreferButton extends Rule {
6832
6850
  }
6833
6851
  }
6834
6852
 
6835
- const defaults$6 = {
6853
+ const defaults$7 = {
6836
6854
  mapping: {
6837
6855
  article: "article",
6838
6856
  banner: "header",
@@ -6862,7 +6880,7 @@ const defaults$6 = {
6862
6880
  };
6863
6881
  class PreferNativeElement extends Rule {
6864
6882
  constructor(options) {
6865
- super({ ...defaults$6, ...options });
6883
+ super({ ...defaults$7, ...options });
6866
6884
  }
6867
6885
  static schema() {
6868
6886
  return {
@@ -6982,7 +7000,7 @@ class PreferTbody extends Rule {
6982
7000
  }
6983
7001
  }
6984
7002
 
6985
- const defaults$5 = {
7003
+ const defaults$6 = {
6986
7004
  target: "all",
6987
7005
  };
6988
7006
  const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
@@ -6992,7 +7010,7 @@ const supportSri = {
6992
7010
  };
6993
7011
  class RequireSri extends Rule {
6994
7012
  constructor(options) {
6995
- super({ ...defaults$5, ...options });
7013
+ super({ ...defaults$6, ...options });
6996
7014
  this.target = this.options.target;
6997
7015
  }
6998
7016
  static schema() {
@@ -7120,6 +7138,155 @@ class SvgFocusable extends Rule {
7120
7138
  }
7121
7139
  }
7122
7140
 
7141
+ const defaults$5 = {
7142
+ characters: [
7143
+ { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
7144
+ { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" },
7145
+ ],
7146
+ ignoreClasses: [],
7147
+ };
7148
+ function constructRegex(characters) {
7149
+ const disallowed = characters
7150
+ .map((it) => {
7151
+ return it.pattern;
7152
+ })
7153
+ .join("|");
7154
+ const pattern = `(${disallowed})`;
7155
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7156
+ return new RegExp(pattern, "g");
7157
+ }
7158
+ function getText(node) {
7159
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
7160
+ const match = node.textContent.match(/^(\s*)(.*)$/);
7161
+ const [, leading, text] = match;
7162
+ return [leading.length, text.trimEnd()];
7163
+ }
7164
+ /**
7165
+ * Node 12 does not support String.matchAll, this simulates it's behavior.
7166
+ */
7167
+ function matchAll(text, regexp) {
7168
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7169
+ const copy = new RegExp(regexp);
7170
+ const matches = [];
7171
+ /* eslint-disable-next-line no-constant-condition */
7172
+ while (true) {
7173
+ const match = copy.exec(text);
7174
+ if (match === null) {
7175
+ break;
7176
+ }
7177
+ matches.push(match);
7178
+ }
7179
+ return matches;
7180
+ }
7181
+ class TelNonBreaking extends Rule {
7182
+ constructor(options) {
7183
+ super({ ...defaults$5, ...options });
7184
+ this.regex = constructRegex(this.options.characters);
7185
+ }
7186
+ static schema() {
7187
+ return {
7188
+ characters: {
7189
+ type: "array",
7190
+ items: {
7191
+ type: "object",
7192
+ additionalProperties: false,
7193
+ properties: {
7194
+ pattern: {
7195
+ type: "string",
7196
+ },
7197
+ replacement: {
7198
+ type: "string",
7199
+ },
7200
+ description: {
7201
+ type: "string",
7202
+ },
7203
+ },
7204
+ },
7205
+ },
7206
+ ignoreClasses: {
7207
+ type: "array",
7208
+ items: {
7209
+ type: "string",
7210
+ },
7211
+ },
7212
+ };
7213
+ }
7214
+ documentation(context) {
7215
+ const { characters } = this.options;
7216
+ const replacements = characters.map((it) => {
7217
+ return ` - \`${it.pattern}\` - replace with \`${it.replacement}\` (${it.description}).`;
7218
+ });
7219
+ return {
7220
+ description: [
7221
+ context
7222
+ ? `The \`${context.pattern}\` character should be replaced with \`${context.replacement}\` character (${context.description}) when used in a telephone number.`
7223
+ : `Replace this character with a non-breaking version.`,
7224
+ "",
7225
+ "Unless non-breaking characters is used there could be a line break inserted at that character.",
7226
+ "Line breaks make is harder to read and understand the telephone number.",
7227
+ "",
7228
+ "The following characters should be avoided:",
7229
+ "",
7230
+ ...replacements,
7231
+ ].join("\n"),
7232
+ url: ruleDocumentationUrl("@/rules/tel-non-breaking.ts"),
7233
+ };
7234
+ }
7235
+ setup() {
7236
+ this.on("element:ready", this.isRelevant, (event) => {
7237
+ const { target } = event;
7238
+ const { ignoreClasses } = this.options;
7239
+ /* skip if element has a class in the ignore list */
7240
+ const isIgnored = ignoreClasses.some((it) => target.classList.contains(it));
7241
+ if (isIgnored) {
7242
+ return;
7243
+ }
7244
+ this.walk(target, target);
7245
+ });
7246
+ }
7247
+ isRelevant(event) {
7248
+ const { target } = event;
7249
+ /* should only deal with anchors */
7250
+ if (!target.is("a")) {
7251
+ return false;
7252
+ }
7253
+ /* ignore if anchor does not have tel href */
7254
+ const attr = target.getAttribute("href");
7255
+ if (!attr || !attr.valueMatches(/^tel:/, false)) {
7256
+ return false;
7257
+ }
7258
+ return true;
7259
+ }
7260
+ walk(anchor, node) {
7261
+ for (const child of node.childNodes) {
7262
+ if (isTextNode(child)) {
7263
+ this.detectDisallowed(anchor, child);
7264
+ }
7265
+ else if (isElementNode(child)) {
7266
+ this.walk(anchor, child);
7267
+ }
7268
+ }
7269
+ }
7270
+ detectDisallowed(anchor, node) {
7271
+ const [offset, text] = getText(node);
7272
+ const matches = matchAll(text, this.regex);
7273
+ for (const match of matches) {
7274
+ const detected = match[0];
7275
+ const entry = this.options.characters.find((it) => it.pattern === detected);
7276
+ /* istanbul ignore next: should never happen and cannot be tested, just a sanity check */
7277
+ if (!entry) {
7278
+ throw new Error(`Failed to find entry for "${detected}" when searching text "${text}"`);
7279
+ }
7280
+ const message = `"${detected}" should be replaced with "${entry.replacement}" (${entry.description}) in telephone number`;
7281
+ const begin = offset + match.index;
7282
+ const end = begin + detected.length;
7283
+ const location = sliceLocation(node.location, begin, end);
7284
+ const context = entry;
7285
+ this.report(anchor, message, location, context);
7286
+ }
7287
+ }
7288
+ }
7289
+
7123
7290
  function hasAltText(image) {
7124
7291
  const alt = image.getAttribute("alt");
7125
7292
  /* missing or boolean */
@@ -9150,9 +9317,6 @@ function parseStyle$1(name) {
9150
9317
  case "selfclose":
9151
9318
  case "selfclosing":
9152
9319
  return Style$1.AlwaysSelfclose;
9153
- /* istanbul ignore next: covered by schema validation */
9154
- default:
9155
- throw new Error(`Invalid style "${name}" for "void" rule`);
9156
9320
  }
9157
9321
  }
9158
9322
 
@@ -9584,6 +9748,7 @@ const bundledRules = {
9584
9748
  "script-element": ScriptElement,
9585
9749
  "script-type": ScriptType,
9586
9750
  "svg-focusable": SvgFocusable,
9751
+ "tel-non-breaking": TelNonBreaking,
9587
9752
  "text-content": TextContent,
9588
9753
  "unrecognized-char-ref": UnknownCharReference,
9589
9754
  void: Void,
@@ -9680,6 +9845,7 @@ const config$1 = {
9680
9845
  "script-element": "error",
9681
9846
  "script-type": "error",
9682
9847
  "svg-focusable": "off",
9848
+ "tel-non-breaking": "error",
9683
9849
  "text-content": "error",
9684
9850
  "unrecognized-char-ref": "error",
9685
9851
  void: "off",
@@ -9785,7 +9951,8 @@ class ResolvedConfig {
9785
9951
  });
9786
9952
  }
9787
9953
  catch (err) {
9788
- throw new NestedError(`When transforming "${source.filename}": ${err.message}`, err);
9954
+ const message = err instanceof Error ? err.message : String(err);
9955
+ throw new NestedError(`When transforming "${source.filename}": ${message}`, err);
9789
9956
  }
9790
9957
  }
9791
9958
  else {
@@ -9861,9 +10028,10 @@ function loadFromFile(filename) {
9861
10028
  }
9862
10029
  /* expand any relative paths */
9863
10030
  for (const key of ["extends", "elements", "plugins"]) {
9864
- if (!json[key])
10031
+ const value = json[key];
10032
+ if (!value)
9865
10033
  continue;
9866
- json[key] = json[key].map((ref) => {
10034
+ json[key] = value.map((ref) => {
9867
10035
  return Config.expandRelative(ref, path.dirname(filename));
9868
10036
  });
9869
10037
  }
@@ -10021,6 +10189,7 @@ class Config {
10021
10189
  /**
10022
10190
  * Get element metadata.
10023
10191
  */
10192
+ /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10024
10193
  getMetaTable() {
10025
10194
  /* use cached table if it exists */
10026
10195
  if (this.metaTable) {
@@ -10059,7 +10228,8 @@ class Config {
10059
10228
  metaTable.loadFromObject(legacyRequire(entry));
10060
10229
  }
10061
10230
  catch (err) {
10062
- throw new ConfigError(`Failed to load elements from "${entry}": ${err.message}`, err);
10231
+ const message = err instanceof Error ? err.message : String(err);
10232
+ throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, err);
10063
10233
  }
10064
10234
  }
10065
10235
  metaTable.init();
@@ -10135,7 +10305,8 @@ class Config {
10135
10305
  return plugin;
10136
10306
  }
10137
10307
  catch (err) {
10138
- throw new ConfigError(`Failed to load plugin "${moduleName}": ${err}`, err);
10308
+ const message = err instanceof Error ? err.message : String(err);
10309
+ throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, err);
10139
10310
  }
10140
10311
  });
10141
10312
  }
@@ -11139,7 +11310,7 @@ class Engine {
11139
11310
  /**
11140
11311
  * Lint sources and return report
11141
11312
  *
11142
- * @param src - Parsed source.
11313
+ * @param sources - Sources to lint.
11143
11314
  * @returns Report output.
11144
11315
  */
11145
11316
  lint(sources) {
@@ -11233,7 +11404,7 @@ class Engine {
11233
11404
  const lines = [];
11234
11405
  function decoration(node) {
11235
11406
  let output = "";
11236
- if (node.hasAttribute("id")) {
11407
+ if (node.id) {
11237
11408
  output += `#${node.id}`;
11238
11409
  }
11239
11410
  if (node.hasAttribute("class")) {
@@ -11989,6 +12160,7 @@ const formatter$4 = checkstyleFormatter;
11989
12160
 
11990
12161
  const defaults = {
11991
12162
  showLink: true,
12163
+ showSummary: true,
11992
12164
  };
11993
12165
  /**
11994
12166
  * Codeframe formatter based on ESLint codeframe.
@@ -12103,9 +12275,11 @@ function codeframe(results, options) {
12103
12275
  return resultsOutput.concat(messages);
12104
12276
  }, [])
12105
12277
  .join("\n");
12106
- output += "\n";
12107
- output += formatSummary(errors, warnings);
12108
- output += "\n";
12278
+ if (merged.showSummary) {
12279
+ output += "\n";
12280
+ output += formatSummary(errors, warnings);
12281
+ output += "\n";
12282
+ }
12109
12283
  return errors + warnings > 0 ? output : "";
12110
12284
  }
12111
12285
  const formatter$3 = codeframe;