html-validate 6.6.1 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cjs/browser.d.ts +1 -1
  2. package/dist/cjs/browser.js +2 -0
  3. package/dist/cjs/browser.js.map +1 -1
  4. package/dist/cjs/cli.js +9 -5
  5. package/dist/cjs/cli.js.map +1 -1
  6. package/dist/cjs/core.d.ts +15 -8
  7. package/dist/cjs/core.js +399 -159
  8. package/dist/cjs/core.js.map +1 -1
  9. package/dist/cjs/html-validate.js +11 -6
  10. package/dist/cjs/html-validate.js.map +1 -1
  11. package/dist/cjs/index.d.ts +2 -2
  12. package/dist/cjs/index.js +2 -0
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/jest-lib.js +83 -65
  15. package/dist/cjs/jest-lib.js.map +1 -1
  16. package/dist/cjs/jest.d.ts +35 -2
  17. package/dist/cjs/jest.js +14 -3
  18. package/dist/cjs/jest.js.map +1 -1
  19. package/dist/cjs/test-utils.d.ts +1 -1
  20. package/dist/es/browser.d.ts +1 -1
  21. package/dist/es/browser.js +2 -0
  22. package/dist/es/browser.js.map +1 -1
  23. package/dist/es/cli.js +9 -5
  24. package/dist/es/cli.js.map +1 -1
  25. package/dist/es/core.d.ts +15 -8
  26. package/dist/es/core.js +322 -103
  27. package/dist/es/core.js.map +1 -1
  28. package/dist/es/html-validate.js +11 -6
  29. package/dist/es/html-validate.js.map +1 -1
  30. package/dist/es/index.d.ts +2 -2
  31. package/dist/es/index.js +2 -0
  32. package/dist/es/index.js.map +1 -1
  33. package/dist/es/jest-lib.js +76 -64
  34. package/dist/es/jest-lib.js.map +1 -1
  35. package/dist/es/jest.d.ts +35 -2
  36. package/dist/es/jest.js +15 -3
  37. package/dist/es/jest.js.map +1 -1
  38. package/dist/es/test-utils.d.ts +1 -1
  39. package/elements/html5.js +1881 -0
  40. package/package.json +38 -29
  41. package/dist/cjs/jest-lib.d.ts +0 -35
  42. package/dist/es/jest-lib.d.ts +0 -35
  43. package/elements/html5.json +0 -1876
package/dist/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
  }
@@ -1200,6 +1202,27 @@ class Attribute {
1200
1202
  }
1201
1203
  }
1202
1204
 
1205
+ function getCSSDeclarations(value) {
1206
+ return value
1207
+ .trim()
1208
+ .split(";")
1209
+ .filter(Boolean)
1210
+ .map((it) => {
1211
+ const [property, value] = it.split(":", 2);
1212
+ return [property.trim(), value ? value.trim() : ""];
1213
+ });
1214
+ }
1215
+ /**
1216
+ * @internal
1217
+ */
1218
+ function parseCssDeclaration(value) {
1219
+ if (!value || value instanceof DynamicValue) {
1220
+ return {};
1221
+ }
1222
+ const pairs = getCSSDeclarations(value);
1223
+ return Object.fromEntries(pairs);
1224
+ }
1225
+
1203
1226
  function sliceSize(size, begin, end) {
1204
1227
  if (typeof size !== "number") {
1205
1228
  return size;
@@ -1838,8 +1861,13 @@ var NodeClosed;
1838
1861
  NodeClosed[NodeClosed["VoidSelfClosed"] = 3] = "VoidSelfClosed";
1839
1862
  NodeClosed[NodeClosed["ImplicitClosed"] = 4] = "ImplicitClosed";
1840
1863
  })(NodeClosed || (NodeClosed = {}));
1841
- function isElement(node) {
1842
- return node.nodeType === NodeType.ELEMENT_NODE;
1864
+ /**
1865
+ * Returns true if the node is an element node.
1866
+ *
1867
+ * @public
1868
+ */
1869
+ function isElementNode(node) {
1870
+ return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
1843
1871
  }
1844
1872
  function isValidTagName(tagName) {
1845
1873
  return Boolean(tagName !== "" && tagName !== "*");
@@ -1913,7 +1941,7 @@ class HtmlElement extends DOMNode {
1913
1941
  * Similar to childNodes but only elements.
1914
1942
  */
1915
1943
  get childElements() {
1916
- return this.childNodes.filter(isElement);
1944
+ return this.childNodes.filter(isElementNode);
1917
1945
  }
1918
1946
  /**
1919
1947
  * Find the first ancestor matching a selector.
@@ -2143,6 +2171,10 @@ class HtmlElement extends DOMNode {
2143
2171
  get id() {
2144
2172
  return this.getAttributeValue("id");
2145
2173
  }
2174
+ get style() {
2175
+ const attr = this.getAttribute("style");
2176
+ return parseCssDeclaration(attr === null || attr === void 0 ? void 0 : attr.value);
2177
+ }
2146
2178
  /**
2147
2179
  * Returns the first child element or null if there are no child elements.
2148
2180
  */
@@ -2174,8 +2206,9 @@ class HtmlElement extends DOMNode {
2174
2206
  }, []);
2175
2207
  }
2176
2208
  querySelector(selector) {
2209
+ var _a;
2177
2210
  const it = this.querySelectorImpl(selector);
2178
- return it.next().value || null;
2211
+ return (_a = it.next().value) !== null && _a !== void 0 ? _a : null; // eslint-disable-line @typescript-eslint/no-unsafe-return
2179
2212
  }
2180
2213
  querySelectorAll(selector) {
2181
2214
  const it = this.querySelectorImpl(selector);
@@ -2742,8 +2775,6 @@ var configurationSchema = {
2742
2775
  };
2743
2776
 
2744
2777
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2745
- const espree = legacyRequire("espree");
2746
- const walk = legacyRequire("acorn-walk");
2747
2778
  function joinTemplateLiteral(nodes) {
2748
2779
  let offset = nodes[0].start + 1;
2749
2780
  let output = "";
@@ -2924,7 +2955,8 @@ class TemplateExtractor {
2924
2955
  extractObjectProperty(key) {
2925
2956
  const result = [];
2926
2957
  const { filename, data } = this;
2927
- walk.simple(this.ast, {
2958
+ const node = this.ast;
2959
+ walk.simple(node, {
2928
2960
  Property(node) {
2929
2961
  if (compareKey(node.key, key, filename)) {
2930
2962
  const source = extractLiteral(node.value, filename, data);
@@ -2948,7 +2980,7 @@ var TRANSFORMER_API;
2948
2980
  /** @public */
2949
2981
  const name = "html-validate";
2950
2982
  /** @public */
2951
- const version = "6.6.1";
2983
+ const version = "6.8.0";
2952
2984
  /** @public */
2953
2985
  const homepage = "https://html-validate.org";
2954
2986
  /** @public */
@@ -3269,7 +3301,7 @@ function ruleDocumentationUrl(filename) {
3269
3301
  return `${homepage}/rules/${normalized}.html`;
3270
3302
  }
3271
3303
 
3272
- const defaults$p = {
3304
+ const defaults$q = {
3273
3305
  allowExternal: true,
3274
3306
  allowRelative: true,
3275
3307
  allowAbsolute: true,
@@ -3313,7 +3345,7 @@ function matchList(value, list) {
3313
3345
  }
3314
3346
  class AllowedLinks extends Rule {
3315
3347
  constructor(options) {
3316
- super({ ...defaults$p, ...options });
3348
+ super({ ...defaults$q, ...options });
3317
3349
  this.allowExternal = parseAllow(this.options.allowExternal);
3318
3350
  this.allowRelative = parseAllow(this.options.allowRelative);
3319
3351
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3592,7 +3624,7 @@ class CaseStyle {
3592
3624
  default: {
3593
3625
  const last = names.slice(-1);
3594
3626
  const rest = names.slice(0, -1);
3595
- return `${rest.join(", ")} or ${last}`;
3627
+ return `${rest.join(", ")} or ${last[0]}`;
3596
3628
  }
3597
3629
  }
3598
3630
  }
@@ -3608,19 +3640,19 @@ class CaseStyle {
3608
3640
  case "camelcase":
3609
3641
  return { pattern: /^[a-z][A-Za-z]*$/, name: "camelCase" };
3610
3642
  default:
3611
- throw new ConfigError(`Invalid style "${style}" for ${ruleId} rule`);
3643
+ throw new ConfigError(`Invalid style "${cur}" for ${ruleId} rule`);
3612
3644
  }
3613
3645
  });
3614
3646
  }
3615
3647
  }
3616
3648
 
3617
- const defaults$o = {
3649
+ const defaults$p = {
3618
3650
  style: "lowercase",
3619
3651
  ignoreForeign: true,
3620
3652
  };
3621
3653
  class AttrCase extends Rule {
3622
3654
  constructor(options) {
3623
- super({ ...defaults$o, ...options });
3655
+ super({ ...defaults$p, ...options });
3624
3656
  this.style = new CaseStyle(this.options.style, "attr-case");
3625
3657
  }
3626
3658
  static schema() {
@@ -3647,8 +3679,11 @@ class AttrCase extends Rule {
3647
3679
  };
3648
3680
  }
3649
3681
  documentation() {
3682
+ const { style } = this.options;
3650
3683
  return {
3651
- description: `Attribute name must be ${this.options.style}.`,
3684
+ description: Array.isArray(style)
3685
+ ? [`Attribute name must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
3686
+ : `Attribute name must be in ${style}.`,
3652
3687
  url: ruleDocumentationUrl("@/rules/attr-case.ts"),
3653
3688
  };
3654
3689
  }
@@ -3939,7 +3974,7 @@ function isRelevant$3(event) {
3939
3974
  class AttrDelimiter extends Rule {
3940
3975
  documentation() {
3941
3976
  return {
3942
- description: `Attribute value should be separated by `,
3977
+ description: `Attribute value must not be separated by whitespace.`,
3943
3978
  url: ruleDocumentationUrl("@/rules/attr-delimiter.ts"),
3944
3979
  };
3945
3980
  }
@@ -3956,7 +3991,7 @@ class AttrDelimiter extends Rule {
3956
3991
  }
3957
3992
 
3958
3993
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
3959
- const defaults$n = {
3994
+ const defaults$o = {
3960
3995
  pattern: DEFAULT_PATTERN,
3961
3996
  ignoreForeign: true,
3962
3997
  };
@@ -3993,7 +4028,7 @@ function generateDescription(name, pattern) {
3993
4028
  }
3994
4029
  class AttrPattern extends Rule {
3995
4030
  constructor(options) {
3996
- super({ ...defaults$n, ...options });
4031
+ super({ ...defaults$o, ...options });
3997
4032
  this.pattern = generateRegexp(this.options.pattern);
3998
4033
  }
3999
4034
  static schema() {
@@ -4053,13 +4088,13 @@ var QuoteStyle;
4053
4088
  QuoteStyle["DOUBLE_QUOTE"] = "\"";
4054
4089
  QuoteStyle["AUTO_QUOTE"] = "auto";
4055
4090
  })(QuoteStyle || (QuoteStyle = {}));
4056
- const defaults$m = {
4091
+ const defaults$n = {
4057
4092
  style: "auto",
4058
4093
  unquoted: false,
4059
4094
  };
4060
4095
  class AttrQuotes extends Rule {
4061
4096
  constructor(options) {
4062
- super({ ...defaults$m, ...options });
4097
+ super({ ...defaults$n, ...options });
4063
4098
  this.style = parseStyle$4(this.options.style);
4064
4099
  }
4065
4100
  static schema() {
@@ -4224,12 +4259,12 @@ class AttributeAllowedValues extends Rule {
4224
4259
  }
4225
4260
  }
4226
4261
 
4227
- const defaults$l = {
4262
+ const defaults$m = {
4228
4263
  style: "omit",
4229
4264
  };
4230
4265
  class AttributeBooleanStyle extends Rule {
4231
4266
  constructor(options) {
4232
- super({ ...defaults$l, ...options });
4267
+ super({ ...defaults$m, ...options });
4233
4268
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4234
4269
  }
4235
4270
  static schema() {
@@ -4305,12 +4340,12 @@ function reportMessage$1(attr, style) {
4305
4340
  return "";
4306
4341
  }
4307
4342
 
4308
- const defaults$k = {
4343
+ const defaults$l = {
4309
4344
  style: "omit",
4310
4345
  };
4311
4346
  class AttributeEmptyStyle extends Rule {
4312
4347
  constructor(options) {
4313
- super({ ...defaults$k, ...options });
4348
+ super({ ...defaults$l, ...options });
4314
4349
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4315
4350
  }
4316
4351
  static schema() {
@@ -4418,12 +4453,12 @@ function describePattern(pattern) {
4418
4453
  }
4419
4454
  }
4420
4455
 
4421
- const defaults$j = {
4456
+ const defaults$k = {
4422
4457
  pattern: "kebabcase",
4423
4458
  };
4424
4459
  class ClassPattern extends Rule {
4425
4460
  constructor(options) {
4426
- super({ ...defaults$j, ...options });
4461
+ super({ ...defaults$k, ...options });
4427
4462
  this.pattern = parsePattern(this.options.pattern);
4428
4463
  }
4429
4464
  static schema() {
@@ -4449,7 +4484,9 @@ class ClassPattern extends Rule {
4449
4484
  classes.forEach((cur, index) => {
4450
4485
  if (!cur.match(this.pattern)) {
4451
4486
  const location = classes.location(index);
4452
- this.report(event.target, `Class "${cur}" does not match required pattern "${this.pattern}"`, location);
4487
+ const pattern = this.pattern.toString();
4488
+ const message = `Class "${cur}" does not match required pattern "${pattern}"`;
4489
+ this.report(event.target, message, location);
4453
4490
  }
4454
4491
  });
4455
4492
  });
@@ -4530,13 +4567,13 @@ class CloseOrder extends Rule {
4530
4567
  }
4531
4568
  }
4532
4569
 
4533
- const defaults$i = {
4570
+ const defaults$j = {
4534
4571
  include: null,
4535
4572
  exclude: null,
4536
4573
  };
4537
4574
  class Deprecated extends Rule {
4538
4575
  constructor(options) {
4539
- super({ ...defaults$i, ...options });
4576
+ super({ ...defaults$j, ...options });
4540
4577
  }
4541
4578
  static schema() {
4542
4579
  return {
@@ -4699,12 +4736,12 @@ class NoStyleTag$1 extends Rule {
4699
4736
  }
4700
4737
  }
4701
4738
 
4702
- const defaults$h = {
4739
+ const defaults$i = {
4703
4740
  style: "uppercase",
4704
4741
  };
4705
4742
  class DoctypeStyle extends Rule {
4706
4743
  constructor(options) {
4707
- super({ ...defaults$h, ...options });
4744
+ super({ ...defaults$i, ...options });
4708
4745
  }
4709
4746
  static schema() {
4710
4747
  return {
@@ -4736,12 +4773,12 @@ class DoctypeStyle extends Rule {
4736
4773
  }
4737
4774
  }
4738
4775
 
4739
- const defaults$g = {
4776
+ const defaults$h = {
4740
4777
  style: "lowercase",
4741
4778
  };
4742
4779
  class ElementCase extends Rule {
4743
4780
  constructor(options) {
4744
- super({ ...defaults$g, ...options });
4781
+ super({ ...defaults$h, ...options });
4745
4782
  this.style = new CaseStyle(this.options.style, "element-case");
4746
4783
  }
4747
4784
  static schema() {
@@ -4765,8 +4802,11 @@ class ElementCase extends Rule {
4765
4802
  };
4766
4803
  }
4767
4804
  documentation() {
4805
+ const { style } = this.options;
4768
4806
  return {
4769
- description: `Element tagname must be ${this.options.style}.`,
4807
+ description: Array.isArray(style)
4808
+ ? [`Element tagname must be in one of:`, "", ...style.map((it) => `- ${it}`)].join("\n")
4809
+ : `Element tagname must be in ${style}.`,
4770
4810
  url: ruleDocumentationUrl("@/rules/element-case.ts"),
4771
4811
  };
4772
4812
  }
@@ -4804,14 +4844,14 @@ class ElementCase extends Rule {
4804
4844
  }
4805
4845
  }
4806
4846
 
4807
- const defaults$f = {
4847
+ const defaults$g = {
4808
4848
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
4809
4849
  whitelist: [],
4810
4850
  blacklist: [],
4811
4851
  };
4812
4852
  class ElementName extends Rule {
4813
4853
  constructor(options) {
4814
- super({ ...defaults$f, ...options });
4854
+ super({ ...defaults$g, ...options });
4815
4855
  // eslint-disable-next-line security/detect-non-literal-regexp
4816
4856
  this.pattern = new RegExp(this.options.pattern);
4817
4857
  }
@@ -4852,7 +4892,7 @@ class ElementName extends Rule {
4852
4892
  ...context.blacklist.map((cur) => `- ${cur}`),
4853
4893
  ];
4854
4894
  }
4855
- if (context.pattern !== defaults$f.pattern) {
4895
+ if (context.pattern !== defaults$g.pattern) {
4856
4896
  return [
4857
4897
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
4858
4898
  "",
@@ -5254,7 +5294,7 @@ class EmptyTitle extends Rule {
5254
5294
  }
5255
5295
  }
5256
5296
 
5257
- const defaults$e = {
5297
+ const defaults$f = {
5258
5298
  allowMultipleH1: false,
5259
5299
  minInitialRank: "h1",
5260
5300
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5285,7 +5325,7 @@ function parseMaxInitial(value) {
5285
5325
  }
5286
5326
  class HeadingLevel extends Rule {
5287
5327
  constructor(options) {
5288
- super({ ...defaults$e, ...options });
5328
+ super({ ...defaults$f, ...options });
5289
5329
  this.stack = [];
5290
5330
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5291
5331
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5443,12 +5483,12 @@ class HeadingLevel extends Rule {
5443
5483
  }
5444
5484
  }
5445
5485
 
5446
- const defaults$d = {
5486
+ const defaults$e = {
5447
5487
  pattern: "kebabcase",
5448
5488
  };
5449
5489
  class IdPattern extends Rule {
5450
5490
  constructor(options) {
5451
- super({ ...defaults$d, ...options });
5491
+ super({ ...defaults$e, ...options });
5452
5492
  this.pattern = parsePattern(this.options.pattern);
5453
5493
  }
5454
5494
  static schema() {
@@ -5467,6 +5507,7 @@ class IdPattern extends Rule {
5467
5507
  }
5468
5508
  setup() {
5469
5509
  this.on("attr", (event) => {
5510
+ var _a;
5470
5511
  if (event.key.toLowerCase() !== "id") {
5471
5512
  return;
5472
5513
  }
@@ -5475,7 +5516,10 @@ class IdPattern extends Rule {
5475
5516
  return;
5476
5517
  }
5477
5518
  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);
5519
+ const value = (_a = event.value) !== null && _a !== void 0 ? _a : "";
5520
+ const pattern = this.pattern.toString();
5521
+ const message = `ID "${value}" does not match required pattern "${pattern}"`;
5522
+ this.report(event.target, message, event.valueLocation);
5479
5523
  }
5480
5524
  });
5481
5525
  }
@@ -5795,12 +5839,12 @@ function findLabelByParent(el) {
5795
5839
  return [];
5796
5840
  }
5797
5841
 
5798
- const defaults$c = {
5842
+ const defaults$d = {
5799
5843
  maxlength: 70,
5800
5844
  };
5801
5845
  class LongTitle extends Rule {
5802
5846
  constructor(options) {
5803
- super({ ...defaults$c, ...options });
5847
+ super({ ...defaults$d, ...options });
5804
5848
  this.maxlength = this.options.maxlength;
5805
5849
  }
5806
5850
  static schema() {
@@ -5947,13 +5991,13 @@ class MultipleLabeledControls extends Rule {
5947
5991
  }
5948
5992
  }
5949
5993
 
5950
- const defaults$b = {
5994
+ const defaults$c = {
5951
5995
  include: null,
5952
5996
  exclude: null,
5953
5997
  };
5954
5998
  class NoAutoplay extends Rule {
5955
5999
  constructor(options) {
5956
- super({ ...defaults$b, ...options });
6000
+ super({ ...defaults$c, ...options });
5957
6001
  }
5958
6002
  documentation(context) {
5959
6003
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6194,24 +6238,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6194
6238
  }
6195
6239
  }
6196
6240
 
6197
- const defaults$a = {
6241
+ const defaults$b = {
6198
6242
  include: null,
6199
6243
  exclude: null,
6200
6244
  allowedProperties: ["display"],
6201
6245
  };
6202
- function getCSSDeclarations(value) {
6203
- return value
6204
- .trim()
6205
- .split(";")
6206
- .filter(Boolean)
6207
- .map((it) => {
6208
- const [property, value] = it.split(":", 2);
6209
- return { property: property.trim(), value: value ? value.trim() : undefined };
6210
- });
6211
- }
6212
6246
  class NoInlineStyle extends Rule {
6213
6247
  constructor(options) {
6214
- super({ ...defaults$a, ...options });
6248
+ super({ ...defaults$b, ...options });
6215
6249
  }
6216
6250
  static schema() {
6217
6251
  return {
@@ -6289,18 +6323,15 @@ class NoInlineStyle extends Rule {
6289
6323
  return true;
6290
6324
  }
6291
6325
  allPropertiesAllowed(value) {
6292
- if (typeof value !== "string") {
6293
- return false;
6294
- }
6295
6326
  const allowProperties = this.options.allowedProperties;
6296
6327
  /* quick path: no properties are allowed, no need to check each one individually */
6297
6328
  if (allowProperties.length === 0) {
6298
6329
  return false;
6299
6330
  }
6300
- const declarations = getCSSDeclarations(value);
6331
+ const declarations = Object.keys(parseCssDeclaration(value));
6301
6332
  return (declarations.length > 0 &&
6302
6333
  declarations.every((it) => {
6303
- return allowProperties.includes(it.property);
6334
+ return allowProperties.includes(it);
6304
6335
  }));
6305
6336
  }
6306
6337
  }
@@ -6416,24 +6447,24 @@ class NoMultipleMain extends Rule {
6416
6447
  }
6417
6448
  }
6418
6449
 
6419
- const defaults$9 = {
6450
+ const defaults$a = {
6420
6451
  relaxed: false,
6421
6452
  };
6422
6453
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
6423
6454
  const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
6424
6455
  const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
6425
- const replacementTable = new Map([
6426
- ['"', "&quot;"],
6427
- ["&", "&amp;"],
6428
- ["'", "&apos;"],
6429
- ["<", "&lt;"],
6430
- ["=", "&equals;"],
6431
- [">", "&gt;"],
6432
- ["`", "&grave;"],
6433
- ]);
6456
+ const replacementTable = {
6457
+ '"': "&quot;",
6458
+ "&": "&amp;",
6459
+ "'": "&apos;",
6460
+ "<": "&lt;",
6461
+ "=": "&equals;",
6462
+ ">": "&gt;",
6463
+ "`": "&grave;",
6464
+ };
6434
6465
  class NoRawCharacters extends Rule {
6435
6466
  constructor(options) {
6436
- super({ ...defaults$9, ...options });
6467
+ super({ ...defaults$a, ...options });
6437
6468
  this.relaxed = this.options.relaxed;
6438
6469
  }
6439
6470
  static schema() {
@@ -6483,7 +6514,6 @@ class NoRawCharacters extends Rule {
6483
6514
  * @param text - The full text to find unescaped raw characters in.
6484
6515
  * @param location - Location of text.
6485
6516
  * @param regexp - Regexp pattern to match using.
6486
- * @param ignore - List of characters to ignore for this text.
6487
6517
  */
6488
6518
  findRawChars(node, text, location, regexp) {
6489
6519
  let match;
@@ -6499,7 +6529,7 @@ class NoRawCharacters extends Rule {
6499
6529
  continue;
6500
6530
  }
6501
6531
  /* determine replacement character and location */
6502
- const replacement = replacementTable.get(char);
6532
+ const replacement = replacementTable[char];
6503
6533
  const charLocation = sliceLocation(location, match.index, match.index + 1);
6504
6534
  /* report as error */
6505
6535
  this.report(node, `Raw "${char}" must be encoded as "${replacement}"`, charLocation);
@@ -6612,13 +6642,13 @@ class NoRedundantRole extends Rule {
6612
6642
  }
6613
6643
 
6614
6644
  const xmlns = /^(.+):.+$/;
6615
- const defaults$8 = {
6645
+ const defaults$9 = {
6616
6646
  ignoreForeign: true,
6617
6647
  ignoreXML: true,
6618
6648
  };
6619
6649
  class NoSelfClosing extends Rule {
6620
6650
  constructor(options) {
6621
- super({ ...defaults$8, ...options });
6651
+ super({ ...defaults$9, ...options });
6622
6652
  }
6623
6653
  static schema() {
6624
6654
  return {
@@ -6751,13 +6781,13 @@ const replacement = {
6751
6781
  reset: '<button type="reset">',
6752
6782
  image: '<button type="button">',
6753
6783
  };
6754
- const defaults$7 = {
6784
+ const defaults$8 = {
6755
6785
  include: null,
6756
6786
  exclude: null,
6757
6787
  };
6758
6788
  class PreferButton extends Rule {
6759
6789
  constructor(options) {
6760
- super({ ...defaults$7, ...options });
6790
+ super({ ...defaults$8, ...options });
6761
6791
  }
6762
6792
  static schema() {
6763
6793
  return {
@@ -6832,7 +6862,7 @@ class PreferButton extends Rule {
6832
6862
  }
6833
6863
  }
6834
6864
 
6835
- const defaults$6 = {
6865
+ const defaults$7 = {
6836
6866
  mapping: {
6837
6867
  article: "article",
6838
6868
  banner: "header",
@@ -6862,7 +6892,7 @@ const defaults$6 = {
6862
6892
  };
6863
6893
  class PreferNativeElement extends Rule {
6864
6894
  constructor(options) {
6865
- super({ ...defaults$6, ...options });
6895
+ super({ ...defaults$7, ...options });
6866
6896
  }
6867
6897
  static schema() {
6868
6898
  return {
@@ -6982,7 +7012,7 @@ class PreferTbody extends Rule {
6982
7012
  }
6983
7013
  }
6984
7014
 
6985
- const defaults$5 = {
7015
+ const defaults$6 = {
6986
7016
  target: "all",
6987
7017
  };
6988
7018
  const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
@@ -6992,7 +7022,7 @@ const supportSri = {
6992
7022
  };
6993
7023
  class RequireSri extends Rule {
6994
7024
  constructor(options) {
6995
- super({ ...defaults$5, ...options });
7025
+ super({ ...defaults$6, ...options });
6996
7026
  this.target = this.options.target;
6997
7027
  }
6998
7028
  static schema() {
@@ -7120,6 +7150,175 @@ class SvgFocusable extends Rule {
7120
7150
  }
7121
7151
  }
7122
7152
 
7153
+ const defaults$5 = {
7154
+ characters: [
7155
+ { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
7156
+ { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" },
7157
+ ],
7158
+ ignoreClasses: [],
7159
+ ignoreStyle: true,
7160
+ };
7161
+ function constructRegex(characters) {
7162
+ const disallowed = characters
7163
+ .map((it) => {
7164
+ return it.pattern;
7165
+ })
7166
+ .join("|");
7167
+ const pattern = `(${disallowed})`;
7168
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7169
+ return new RegExp(pattern, "g");
7170
+ }
7171
+ function getText(node) {
7172
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
7173
+ const match = node.textContent.match(/^(\s*)(.*)$/);
7174
+ const [, leading, text] = match;
7175
+ return [leading.length, text.trimEnd()];
7176
+ }
7177
+ /**
7178
+ * Node 12 does not support String.matchAll, this simulates it's behavior.
7179
+ */
7180
+ function matchAll(text, regexp) {
7181
+ /* eslint-disable-next-line security/detect-non-literal-regexp */
7182
+ const copy = new RegExp(regexp);
7183
+ const matches = [];
7184
+ /* eslint-disable-next-line no-constant-condition */
7185
+ while (true) {
7186
+ const match = copy.exec(text);
7187
+ if (match === null) {
7188
+ break;
7189
+ }
7190
+ matches.push(match);
7191
+ }
7192
+ return matches;
7193
+ }
7194
+ class TelNonBreaking extends Rule {
7195
+ constructor(options) {
7196
+ super({ ...defaults$5, ...options });
7197
+ this.regex = constructRegex(this.options.characters);
7198
+ }
7199
+ static schema() {
7200
+ return {
7201
+ characters: {
7202
+ type: "array",
7203
+ items: {
7204
+ type: "object",
7205
+ additionalProperties: false,
7206
+ properties: {
7207
+ pattern: {
7208
+ type: "string",
7209
+ },
7210
+ replacement: {
7211
+ type: "string",
7212
+ },
7213
+ description: {
7214
+ type: "string",
7215
+ },
7216
+ },
7217
+ },
7218
+ },
7219
+ ignoreClasses: {
7220
+ type: "array",
7221
+ items: {
7222
+ type: "string",
7223
+ },
7224
+ },
7225
+ ignoreStyle: {
7226
+ type: "boolean",
7227
+ },
7228
+ };
7229
+ }
7230
+ documentation(context) {
7231
+ const { characters } = this.options;
7232
+ const replacements = characters.map((it) => {
7233
+ return ` - \`${it.pattern}\` - replace with \`${it.replacement}\` (${it.description}).`;
7234
+ });
7235
+ return {
7236
+ description: [
7237
+ context
7238
+ ? `The \`${context.pattern}\` character should be replaced with \`${context.replacement}\` character (${context.description}) when used in a telephone number.`
7239
+ : `Replace this character with a non-breaking version.`,
7240
+ "",
7241
+ "Unless non-breaking characters is used there could be a line break inserted at that character.",
7242
+ "Line breaks make is harder to read and understand the telephone number.",
7243
+ "",
7244
+ "The following characters should be avoided:",
7245
+ "",
7246
+ ...replacements,
7247
+ ].join("\n"),
7248
+ url: ruleDocumentationUrl("@/rules/tel-non-breaking.ts"),
7249
+ };
7250
+ }
7251
+ setup() {
7252
+ this.on("element:ready", this.isRelevant, (event) => {
7253
+ const { target } = event;
7254
+ if (this.isIgnored(target)) {
7255
+ return;
7256
+ }
7257
+ this.walk(target, target);
7258
+ });
7259
+ }
7260
+ isRelevant(event) {
7261
+ const { target } = event;
7262
+ /* should only deal with anchors */
7263
+ if (!target.is("a")) {
7264
+ return false;
7265
+ }
7266
+ /* ignore if anchor does not have tel href */
7267
+ const attr = target.getAttribute("href");
7268
+ if (!attr || !attr.valueMatches(/^tel:/, false)) {
7269
+ return false;
7270
+ }
7271
+ return true;
7272
+ }
7273
+ isIgnoredClass(node) {
7274
+ const { ignoreClasses } = this.options;
7275
+ const { classList } = node;
7276
+ return ignoreClasses.some((it) => classList.contains(it));
7277
+ }
7278
+ isIgnoredStyle(node) {
7279
+ const { ignoreStyle } = this.options;
7280
+ const { style } = node;
7281
+ if (!ignoreStyle) {
7282
+ return false;
7283
+ }
7284
+ if (style["white-space"] === "nowrap" || style["white-space"] === "pre") {
7285
+ return true;
7286
+ }
7287
+ return false;
7288
+ }
7289
+ isIgnored(node) {
7290
+ return this.isIgnoredClass(node) || this.isIgnoredStyle(node);
7291
+ }
7292
+ walk(anchor, node) {
7293
+ for (const child of node.childNodes) {
7294
+ if (isTextNode(child)) {
7295
+ this.detectDisallowed(anchor, child);
7296
+ }
7297
+ else if (isElementNode(child)) {
7298
+ this.walk(anchor, child);
7299
+ }
7300
+ }
7301
+ }
7302
+ detectDisallowed(anchor, node) {
7303
+ const [offset, text] = getText(node);
7304
+ const matches = matchAll(text, this.regex);
7305
+ for (const match of matches) {
7306
+ const detected = match[0];
7307
+ const entry = this.options.characters.find((it) => it.pattern === detected);
7308
+ /* istanbul ignore next: should never happen and cannot be tested, just a sanity check */
7309
+ if (!entry) {
7310
+ throw new Error(`Failed to find entry for "${detected}" when searching text "${text}"`);
7311
+ }
7312
+ const message = `"${detected}" should be replaced with "${entry.replacement}" (${entry.description}) in telephone number`;
7313
+ const begin = offset + match.index;
7314
+ const end = begin + detected.length;
7315
+ const location = sliceLocation(node.location, begin, end);
7316
+ const context = entry;
7317
+ this.report(anchor, message, location, context);
7318
+ }
7319
+ }
7320
+ }
7321
+
7123
7322
  function hasAltText(image) {
7124
7323
  const alt = image.getAttribute("alt");
7125
7324
  /* missing or boolean */
@@ -9150,9 +9349,6 @@ function parseStyle$1(name) {
9150
9349
  case "selfclose":
9151
9350
  case "selfclosing":
9152
9351
  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
9352
  }
9157
9353
  }
9158
9354
 
@@ -9584,6 +9780,7 @@ const bundledRules = {
9584
9780
  "script-element": ScriptElement,
9585
9781
  "script-type": ScriptType,
9586
9782
  "svg-focusable": SvgFocusable,
9783
+ "tel-non-breaking": TelNonBreaking,
9587
9784
  "text-content": TextContent,
9588
9785
  "unrecognized-char-ref": UnknownCharReference,
9589
9786
  void: Void,
@@ -9680,6 +9877,7 @@ const config$1 = {
9680
9877
  "script-element": "error",
9681
9878
  "script-type": "error",
9682
9879
  "svg-focusable": "off",
9880
+ "tel-non-breaking": "error",
9683
9881
  "text-content": "error",
9684
9882
  "unrecognized-char-ref": "error",
9685
9883
  void: "off",
@@ -9785,7 +9983,8 @@ class ResolvedConfig {
9785
9983
  });
9786
9984
  }
9787
9985
  catch (err) {
9788
- throw new NestedError(`When transforming "${source.filename}": ${err.message}`, err);
9986
+ const message = err instanceof Error ? err.message : String(err);
9987
+ throw new NestedError(`When transforming "${source.filename}": ${message}`, err);
9789
9988
  }
9790
9989
  }
9791
9990
  else {
@@ -9861,9 +10060,10 @@ function loadFromFile(filename) {
9861
10060
  }
9862
10061
  /* expand any relative paths */
9863
10062
  for (const key of ["extends", "elements", "plugins"]) {
9864
- if (!json[key])
10063
+ const value = json[key];
10064
+ if (!value)
9865
10065
  continue;
9866
- json[key] = json[key].map((ref) => {
10066
+ json[key] = value.map((ref) => {
9867
10067
  return Config.expandRelative(ref, path.dirname(filename));
9868
10068
  });
9869
10069
  }
@@ -10021,6 +10221,7 @@ class Config {
10021
10221
  /**
10022
10222
  * Get element metadata.
10023
10223
  */
10224
+ /* eslint-disable-next-line complexity, sonarjs/cognitive-complexity */
10024
10225
  getMetaTable() {
10025
10226
  /* use cached table if it exists */
10026
10227
  if (this.metaTable) {
@@ -10043,7 +10244,7 @@ class Config {
10043
10244
  }
10044
10245
  let filename;
10045
10246
  /* try searching builtin metadata */
10046
- filename = path.join(projectRoot, "elements", `${entry}.json`);
10247
+ filename = path.join(projectRoot, "elements", `${entry}.js`);
10047
10248
  if (fs.existsSync(filename)) {
10048
10249
  metaTable.loadFromFile(filename);
10049
10250
  continue;
@@ -10059,7 +10260,8 @@ class Config {
10059
10260
  metaTable.loadFromObject(legacyRequire(entry));
10060
10261
  }
10061
10262
  catch (err) {
10062
- throw new ConfigError(`Failed to load elements from "${entry}": ${err.message}`, err);
10263
+ const message = err instanceof Error ? err.message : String(err);
10264
+ throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, err);
10063
10265
  }
10064
10266
  }
10065
10267
  metaTable.init();
@@ -10135,7 +10337,8 @@ class Config {
10135
10337
  return plugin;
10136
10338
  }
10137
10339
  catch (err) {
10138
- throw new ConfigError(`Failed to load plugin "${moduleName}": ${err}`, err);
10340
+ const message = err instanceof Error ? err.message : String(err);
10341
+ throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, err);
10139
10342
  }
10140
10343
  });
10141
10344
  }
@@ -11000,6 +11203,12 @@ class Parser {
11000
11203
  }
11001
11204
  }
11002
11205
 
11206
+ function freeze(src) {
11207
+ return {
11208
+ ...src,
11209
+ selector: src.selector(),
11210
+ };
11211
+ }
11003
11212
  /**
11004
11213
  * @internal
11005
11214
  */
@@ -11051,7 +11260,9 @@ class Reporter {
11051
11260
  line: location.line,
11052
11261
  column: location.column,
11053
11262
  size: location.size || 0,
11054
- selector: node ? node.generateSelector() : null,
11263
+ selector() {
11264
+ return node ? node.generateSelector() : null;
11265
+ },
11055
11266
  context,
11056
11267
  });
11057
11268
  }
@@ -11065,7 +11276,7 @@ class Reporter {
11065
11276
  const report = {
11066
11277
  valid: this.isValid(),
11067
11278
  results: Object.keys(this.result).map((filePath) => {
11068
- const messages = Array.from(this.result[filePath]).sort(messageSort);
11279
+ const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
11069
11280
  const source = (sources || []).find((source) => { var _a; return filePath === ((_a = source.filename) !== null && _a !== void 0 ? _a : ""); });
11070
11281
  return {
11071
11282
  filePath,
@@ -11139,7 +11350,7 @@ class Engine {
11139
11350
  /**
11140
11351
  * Lint sources and return report
11141
11352
  *
11142
- * @param src - Parsed source.
11353
+ * @param sources - Sources to lint.
11143
11354
  * @returns Report output.
11144
11355
  */
11145
11356
  lint(sources) {
@@ -11233,7 +11444,7 @@ class Engine {
11233
11444
  const lines = [];
11234
11445
  function decoration(node) {
11235
11446
  let output = "";
11236
- if (node.hasAttribute("id")) {
11447
+ if (node.id) {
11237
11448
  output += `#${node.id}`;
11238
11449
  }
11239
11450
  if (node.hasAttribute("class")) {
@@ -11467,7 +11678,7 @@ class Engine {
11467
11678
  line: location.line,
11468
11679
  column: location.column,
11469
11680
  size: location.size || 0,
11470
- selector: null,
11681
+ selector: () => null,
11471
11682
  });
11472
11683
  }
11473
11684
  }
@@ -11989,6 +12200,8 @@ const formatter$4 = checkstyleFormatter;
11989
12200
 
11990
12201
  const defaults = {
11991
12202
  showLink: true,
12203
+ showSummary: true,
12204
+ showSelector: false,
11992
12205
  };
11993
12206
  /**
11994
12207
  * Codeframe formatter based on ESLint codeframe.
@@ -12044,6 +12257,7 @@ function getEndLocation(message, source) {
12044
12257
  * @returns The formatted output.
12045
12258
  */
12046
12259
  function formatMessage(message, parentResult, options) {
12260
+ var _a;
12047
12261
  const type = message.severity === 2 ? kleur.red("error") : kleur.yellow("warning");
12048
12262
  const msg = `${kleur.bold(message.message.replace(/([^ ])\.$/, "$1"))}`;
12049
12263
  const ruleId = kleur.dim(`(${message.ruleId})`);
@@ -12066,6 +12280,9 @@ function formatMessage(message, parentResult, options) {
12066
12280
  end: getEndLocation(message, sourceCode),
12067
12281
  }, { highlightCode: false }));
12068
12282
  }
12283
+ if (options.showSelector) {
12284
+ result.push(`${kleur.bold("Selector:")} ${(_a = message.selector) !== null && _a !== void 0 ? _a : "-"}`);
12285
+ }
12069
12286
  if (options.showLink && message.ruleUrl) {
12070
12287
  result.push(`${kleur.bold("Details:")} ${message.ruleUrl}`);
12071
12288
  }
@@ -12103,9 +12320,11 @@ function codeframe(results, options) {
12103
12320
  return resultsOutput.concat(messages);
12104
12321
  }, [])
12105
12322
  .join("\n");
12106
- output += "\n";
12107
- output += formatSummary(errors, warnings);
12108
- output += "\n";
12323
+ if (merged.showSummary) {
12324
+ output += "\n";
12325
+ output += formatSummary(errors, warnings);
12326
+ output += "\n";
12327
+ }
12109
12328
  return errors + warnings > 0 ? output : "";
12110
12329
  }
12111
12330
  const formatter$3 = codeframe;
@@ -12186,5 +12405,5 @@ function getFormatter(name) {
12186
12405
  return (_a = availableFormatters[name]) !== null && _a !== void 0 ? _a : null;
12187
12406
  }
12188
12407
 
12189
- export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, MetaCopyableProperty as f, Reporter as g, TemplateExtractor as h, getFormatter as i, compatibilityCheck as j, TokenType as k, legacyRequire as l, bugs as m, name as n, presets as p, ruleExists as r, version as v };
12408
+ export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, MetaCopyableProperty as f, Reporter as g, TemplateExtractor as h, getFormatter as i, compatibilityCheck as j, codeframe as k, legacyRequire as l, bugs as m, name as n, presets as p, ruleExists as r, version as v };
12190
12409
  //# sourceMappingURL=core.js.map