html-validate 7.10.1 → 7.11.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.
Files changed (48) hide show
  1. package/dist/cjs/browser.d.ts +1 -1
  2. package/dist/cjs/browser.js +2 -1
  3. package/dist/cjs/browser.js.map +1 -1
  4. package/dist/cjs/cli.js +0 -1
  5. package/dist/cjs/cli.js.map +1 -1
  6. package/dist/cjs/core.d.ts +4 -4
  7. package/dist/cjs/core.js +204 -107
  8. package/dist/cjs/core.js.map +1 -1
  9. package/dist/cjs/elements.js +5 -1
  10. package/dist/cjs/elements.js.map +1 -1
  11. package/dist/cjs/html-validate.js +9 -2
  12. package/dist/cjs/html-validate.js.map +1 -1
  13. package/dist/cjs/index.d.ts +1 -1
  14. package/dist/cjs/index.js +2 -1
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/jest-lib.js +0 -1
  17. package/dist/cjs/jest-lib.js.map +1 -1
  18. package/dist/cjs/jest.d.ts +1 -0
  19. package/dist/cjs/jest.js +2 -2
  20. package/dist/cjs/meta-helper.d.ts +1 -0
  21. package/dist/cjs/rules-helper.d.ts +10 -1
  22. package/dist/cjs/rules-helper.js +54 -1
  23. package/dist/cjs/rules-helper.js.map +1 -1
  24. package/dist/cjs/test-utils.d.ts +1 -0
  25. package/dist/es/browser.d.ts +1 -1
  26. package/dist/es/browser.js +2 -2
  27. package/dist/es/cli.js +0 -1
  28. package/dist/es/cli.js.map +1 -1
  29. package/dist/es/core.d.ts +4 -4
  30. package/dist/es/core.js +205 -108
  31. package/dist/es/core.js.map +1 -1
  32. package/dist/es/elements.js +5 -1
  33. package/dist/es/elements.js.map +1 -1
  34. package/dist/es/html-validate.js +10 -3
  35. package/dist/es/html-validate.js.map +1 -1
  36. package/dist/es/index.d.ts +1 -1
  37. package/dist/es/index.js +2 -2
  38. package/dist/es/jest-lib.js +0 -1
  39. package/dist/es/jest-lib.js.map +1 -1
  40. package/dist/es/jest.d.ts +1 -0
  41. package/dist/es/jest.js +2 -2
  42. package/dist/es/meta-helper.d.ts +1 -0
  43. package/dist/es/rules-helper.d.ts +10 -1
  44. package/dist/es/rules-helper.js +53 -2
  45. package/dist/es/rules-helper.js.map +1 -1
  46. package/dist/es/test-utils.d.ts +1 -0
  47. package/dist/schema/elements.json +3 -0
  48. package/package.json +17 -17
package/dist/cjs/core.js CHANGED
@@ -7,10 +7,10 @@ var Ajv = require('ajv');
7
7
  var deepmerge = require('deepmerge');
8
8
  var espree = require('espree');
9
9
  var walk = require('acorn-walk');
10
- var elements = require('./elements.js');
11
10
  var path = require('path');
12
11
  var semver = require('semver');
13
12
  var kleur = require('kleur');
13
+ var elements = require('./elements.js');
14
14
  var codeFrame = require('@babel/code-frame');
15
15
  var stylishImpl = require('@html-validate/stylish');
16
16
 
@@ -1672,6 +1672,7 @@ class NestedError extends Error {
1672
1672
  constructor(message, nested) {
1673
1673
  super(message);
1674
1674
  Error.captureStackTrace(this, NestedError);
1675
+ this.name = NestedError.name;
1675
1676
  if (nested && nested.stack) {
1676
1677
  this.stack += `\nCaused by: ${nested.stack}`;
1677
1678
  }
@@ -1682,6 +1683,44 @@ class NestedError extends Error {
1682
1683
  * @public
1683
1684
  */
1684
1685
  class UserError extends NestedError {
1686
+ constructor(message, nested) {
1687
+ super(message, nested);
1688
+ Error.captureStackTrace(this, UserError);
1689
+ this.name = UserError.name;
1690
+ }
1691
+ prettyFormat() {
1692
+ return undefined;
1693
+ }
1694
+ }
1695
+
1696
+ /**
1697
+ * @internal
1698
+ */
1699
+ class InheritError extends UserError {
1700
+ constructor({ tagName, inherit }) {
1701
+ const message = `Element <${tagName}> cannot inherit from <${inherit}>: no such element`;
1702
+ super(message);
1703
+ Error.captureStackTrace(this, InheritError);
1704
+ this.name = InheritError.name;
1705
+ this.tagName = tagName;
1706
+ this.inherit = inherit;
1707
+ this.filename = null;
1708
+ }
1709
+ prettyFormat() {
1710
+ const { message, tagName, inherit } = this;
1711
+ const source = this.filename
1712
+ ? ["", "This error occurred when loading element metadata from:", `"${this.filename}"`, ""]
1713
+ : [""];
1714
+ return [
1715
+ message,
1716
+ ...source,
1717
+ "This usually occurs when the elements are defined in the wrong order, try one of the following:",
1718
+ "",
1719
+ ` - Ensure the spelling of "${inherit}" is correct.`,
1720
+ ` - Ensure the file containing "${inherit}" is loaded before the file containing "${tagName}".`,
1721
+ ` - Move the definition of "${inherit}" above the definition for "${tagName}".`,
1722
+ ].join("\n");
1723
+ }
1685
1724
  }
1686
1725
 
1687
1726
  function getSummary(schema, obj, errors) {
@@ -2105,6 +2144,9 @@ const definitions = {
2105
2144
  items: {
2106
2145
  type: "string"
2107
2146
  }
2147
+ },
2148
+ {
2149
+ type: "null"
2108
2150
  }
2109
2151
  ]
2110
2152
  }
@@ -2415,6 +2457,10 @@ class MetaTable {
2415
2457
  this.loadFromObject(data, filename);
2416
2458
  }
2417
2459
  catch (err) {
2460
+ if (err instanceof InheritError) {
2461
+ err.filename = filename;
2462
+ throw err;
2463
+ }
2418
2464
  if (err instanceof SchemaValidationError) {
2419
2465
  throw err;
2420
2466
  }
@@ -2467,7 +2513,10 @@ class MetaTable {
2467
2513
  const name = entry.inherit;
2468
2514
  parent = this.elements[name];
2469
2515
  if (!parent) {
2470
- throw new UserError(`Element <${tagName}> cannot inherit from <${name}>: no such element`);
2516
+ throw new InheritError({
2517
+ tagName,
2518
+ inherit: name,
2519
+ });
2471
2520
  }
2472
2521
  }
2473
2522
  /* merge all sources together */
@@ -2646,29 +2695,40 @@ class Validator {
2646
2695
  *
2647
2696
  * For instance, a `<table>` element can only contain a single `<tbody>`
2648
2697
  * child. If multiple `<tbody>` exists this test will fail both nodes.
2698
+ * Note that this is called on the parent but will fail the children violating
2699
+ * the rule.
2649
2700
  *
2650
- * @param node - Element to test.
2651
- * @param rules - List of rules.
2652
- * @param numSiblings - How many siblings of the same type as the element
2653
- * exists (including the element itself)
2654
- * @returns `true` if the element passes the test.
2701
+ * @param children - Array of children to validate.
2702
+ * @param rules - List of rules of the parent element.
2703
+ * @returns `true` if the parent element of the children passes the test.
2655
2704
  */
2656
- static validateOccurrences(node, rules, numSiblings) {
2705
+ static validateOccurrences(children, rules, cb) {
2657
2706
  if (!rules) {
2658
2707
  return true;
2659
2708
  }
2660
- const category = rules.find((cur) => {
2709
+ let valid = true;
2710
+ for (const rule of rules) {
2661
2711
  /** @todo handle complex rules and not just plain arrays (but as of now
2662
2712
  * there is no use-case for it) */
2663
2713
  // istanbul ignore next
2664
- if (typeof cur !== "string") {
2714
+ if (typeof rule !== "string") {
2665
2715
  return false;
2666
2716
  }
2667
- const match = cur.match(/^(.*?)[?*]?$/);
2668
- return match && match[1] === node.tagName;
2669
- });
2670
- const limit = parseAmountQualifier(category);
2671
- return limit === null || numSiblings <= limit;
2717
+ // Check if the rule has a quantifier
2718
+ const [, category, quantifier] = rule.match(/^(@?.*?)([?*]?)$/);
2719
+ const limit = category && quantifier && parseQuantifier(quantifier);
2720
+ if (limit) {
2721
+ const siblings = children.filter((cur) => Validator.validatePermittedCategory(cur, rule, true));
2722
+ if (siblings.length > limit) {
2723
+ // fail only the children above the limit (currently limit can only be 1)
2724
+ for (const child of siblings.slice(limit)) {
2725
+ cb(child, category);
2726
+ }
2727
+ valid = false;
2728
+ }
2729
+ }
2730
+ }
2731
+ return valid;
2672
2732
  }
2673
2733
  /**
2674
2734
  * Validate elements order.
@@ -2730,14 +2790,15 @@ class Validator {
2730
2790
  * Check if an element has the required set of elements. At least one of the
2731
2791
  * selectors must match.
2732
2792
  *
2733
- * Returns [] when valid or a list of tagNames missing as content.
2793
+ * Returns `[]` when valid or a list of required but missing tagnames or
2794
+ * categories.
2734
2795
  */
2735
2796
  static validateRequiredContent(node, rules) {
2736
2797
  if (!rules || rules.length === 0) {
2737
2798
  return [];
2738
2799
  }
2739
2800
  return rules.filter((tagName) => {
2740
- const haveMatchingChild = node.childElements.some((child) => child.is(tagName));
2801
+ const haveMatchingChild = node.childElements.some((child) => Validator.validatePermittedCategory(child, tagName, false));
2741
2802
  return !haveMatchingChild;
2742
2803
  });
2743
2804
  }
@@ -2841,16 +2902,16 @@ class Validator {
2841
2902
  */
2842
2903
  // eslint-disable-next-line complexity
2843
2904
  static validatePermittedCategory(node, category, defaultMatch) {
2905
+ const [, rawCategory] = category.match(/^(@?.*?)([?*]?)$/);
2844
2906
  /* match tagName when an explicit name is given */
2845
- if (category[0] !== "@") {
2846
- const [, tagName] = category.match(/^(.*?)[?*]?$/);
2847
- return node.tagName === tagName;
2907
+ if (rawCategory[0] !== "@") {
2908
+ return node.tagName === rawCategory;
2848
2909
  }
2849
2910
  /* if the meta entry is missing assume any content model would match */
2850
2911
  if (!node.meta) {
2851
2912
  return defaultMatch;
2852
2913
  }
2853
- switch (category) {
2914
+ switch (rawCategory) {
2854
2915
  case "@meta":
2855
2916
  return node.meta.metadata;
2856
2917
  case "@flow":
@@ -2882,23 +2943,15 @@ function validateKeys(rule) {
2882
2943
  }
2883
2944
  }
2884
2945
  }
2885
- function parseAmountQualifier(category) {
2886
- if (!category) {
2887
- /* content not allowed, catched by another rule so just assume unlimited
2888
- * usage for this purpose */
2889
- return null;
2890
- }
2891
- const [, qualifier] = category.match(/^.*?([?*]?)$/);
2892
- switch (qualifier) {
2946
+ function parseQuantifier(quantifier) {
2947
+ switch (quantifier) {
2893
2948
  case "?":
2894
2949
  return 1;
2895
- case "":
2896
- return null;
2897
2950
  case "*":
2898
2951
  return null;
2899
- /* istanbul ignore next */
2952
+ // istanbul ignore next
2900
2953
  default:
2901
- throw new Error(`Invalid amount qualifier "${qualifier}" used`);
2954
+ throw new Error(`Invalid quantifier "${quantifier}" used`);
2902
2955
  }
2903
2956
  }
2904
2957
 
@@ -3430,16 +3483,7 @@ class Rule {
3430
3483
  * `exclude`.
3431
3484
  */
3432
3485
  isKeywordIgnored(keyword, matcher = (list, it) => list.includes(it)) {
3433
- const { include, exclude } = this.options;
3434
- /* ignore keyword if not present in "include" */
3435
- if (include && !matcher(include, keyword)) {
3436
- return true;
3437
- }
3438
- /* ignore keyword if present in "excludes" */
3439
- if (exclude && matcher(exclude, keyword)) {
3440
- return true;
3441
- }
3442
- return false;
3486
+ return rulesHelper.isKeywordIgnored(this.options, keyword, matcher);
3443
3487
  }
3444
3488
  /**
3445
3489
  * Find all tags which has enabled given property.
@@ -3565,7 +3609,7 @@ class Rule {
3565
3609
  }
3566
3610
  }
3567
3611
 
3568
- const defaults$u = {
3612
+ const defaults$v = {
3569
3613
  allowExternal: true,
3570
3614
  allowRelative: true,
3571
3615
  allowAbsolute: true,
@@ -3609,7 +3653,7 @@ function matchList(value, list) {
3609
3653
  }
3610
3654
  class AllowedLinks extends Rule {
3611
3655
  constructor(options) {
3612
- super({ ...defaults$u, ...options });
3656
+ super({ ...defaults$v, ...options });
3613
3657
  this.allowExternal = parseAllow(this.options.allowExternal);
3614
3658
  this.allowRelative = parseAllow(this.options.allowRelative);
3615
3659
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3757,7 +3801,7 @@ var RuleContext$1;
3757
3801
  RuleContext["MISSING_ALT"] = "missing-alt";
3758
3802
  RuleContext["MISSING_HREF"] = "missing-href";
3759
3803
  })(RuleContext$1 || (RuleContext$1 = {}));
3760
- const defaults$t = {
3804
+ const defaults$u = {
3761
3805
  accessible: true,
3762
3806
  };
3763
3807
  function findByTarget(target, siblings) {
@@ -3795,7 +3839,7 @@ function getDescription$1(context) {
3795
3839
  }
3796
3840
  class AreaAlt extends Rule {
3797
3841
  constructor(options) {
3798
- super({ ...defaults$t, ...options });
3842
+ super({ ...defaults$u, ...options });
3799
3843
  }
3800
3844
  static schema() {
3801
3845
  return {
@@ -3957,15 +4001,20 @@ class AriaLabelMisuse extends Rule {
3957
4001
  * @public
3958
4002
  */
3959
4003
  class ConfigError extends UserError {
4004
+ constructor(message, nested) {
4005
+ super(message, nested);
4006
+ Error.captureStackTrace(this, ConfigError);
4007
+ this.name = ConfigError.name;
4008
+ }
3960
4009
  }
3961
4010
 
3962
- const defaults$s = {
4011
+ const defaults$t = {
3963
4012
  style: "lowercase",
3964
4013
  ignoreForeign: true,
3965
4014
  };
3966
4015
  class AttrCase extends Rule {
3967
4016
  constructor(options) {
3968
- super({ ...defaults$s, ...options });
4017
+ super({ ...defaults$t, ...options });
3969
4018
  this.style = new rulesHelper.CaseStyle(this.options.style, "attr-case");
3970
4019
  }
3971
4020
  static schema() {
@@ -4310,7 +4359,7 @@ class AttrDelimiter extends Rule {
4310
4359
  }
4311
4360
 
4312
4361
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4313
- const defaults$r = {
4362
+ const defaults$s = {
4314
4363
  pattern: DEFAULT_PATTERN,
4315
4364
  ignoreForeign: true,
4316
4365
  };
@@ -4347,7 +4396,7 @@ function generateDescription(name, pattern) {
4347
4396
  }
4348
4397
  class AttrPattern extends Rule {
4349
4398
  constructor(options) {
4350
- super({ ...defaults$r, ...options });
4399
+ super({ ...defaults$s, ...options });
4351
4400
  this.pattern = generateRegexp(this.options.pattern);
4352
4401
  }
4353
4402
  static schema() {
@@ -4408,7 +4457,7 @@ var QuoteStyle;
4408
4457
  QuoteStyle["AUTO_QUOTE"] = "auto";
4409
4458
  QuoteStyle["ANY_QUOTE"] = "any";
4410
4459
  })(QuoteStyle || (QuoteStyle = {}));
4411
- const defaults$q = {
4460
+ const defaults$r = {
4412
4461
  style: "auto",
4413
4462
  unquoted: false,
4414
4463
  };
@@ -4475,7 +4524,7 @@ class AttrQuotes extends Rule {
4475
4524
  };
4476
4525
  }
4477
4526
  constructor(options) {
4478
- super({ ...defaults$q, ...options });
4527
+ super({ ...defaults$r, ...options });
4479
4528
  this.style = parseStyle$4(this.options.style);
4480
4529
  }
4481
4530
  setup() {
@@ -4645,12 +4694,12 @@ class AttributeAllowedValues extends Rule {
4645
4694
  }
4646
4695
  }
4647
4696
 
4648
- const defaults$p = {
4697
+ const defaults$q = {
4649
4698
  style: "omit",
4650
4699
  };
4651
4700
  class AttributeBooleanStyle extends Rule {
4652
4701
  constructor(options) {
4653
- super({ ...defaults$p, ...options });
4702
+ super({ ...defaults$q, ...options });
4654
4703
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4655
4704
  }
4656
4705
  static schema() {
@@ -4726,12 +4775,12 @@ function reportMessage$1(attr, style) {
4726
4775
  return "";
4727
4776
  }
4728
4777
 
4729
- const defaults$o = {
4778
+ const defaults$p = {
4730
4779
  style: "omit",
4731
4780
  };
4732
4781
  class AttributeEmptyStyle extends Rule {
4733
4782
  constructor(options) {
4734
- super({ ...defaults$o, ...options });
4783
+ super({ ...defaults$p, ...options });
4735
4784
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4736
4785
  }
4737
4786
  static schema() {
@@ -4887,12 +4936,12 @@ function describePattern(pattern) {
4887
4936
  }
4888
4937
  }
4889
4938
 
4890
- const defaults$n = {
4939
+ const defaults$o = {
4891
4940
  pattern: "kebabcase",
4892
4941
  };
4893
4942
  class ClassPattern extends Rule {
4894
4943
  constructor(options) {
4895
- super({ ...defaults$n, ...options });
4944
+ super({ ...defaults$o, ...options });
4896
4945
  this.pattern = parsePattern(this.options.pattern);
4897
4946
  }
4898
4947
  static schema() {
@@ -5001,13 +5050,13 @@ class CloseOrder extends Rule {
5001
5050
  }
5002
5051
  }
5003
5052
 
5004
- const defaults$m = {
5053
+ const defaults$n = {
5005
5054
  include: null,
5006
5055
  exclude: null,
5007
5056
  };
5008
5057
  class Deprecated extends Rule {
5009
5058
  constructor(options) {
5010
- super({ ...defaults$m, ...options });
5059
+ super({ ...defaults$n, ...options });
5011
5060
  }
5012
5061
  static schema() {
5013
5062
  return {
@@ -5170,12 +5219,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5170
5219
  }
5171
5220
  };
5172
5221
 
5173
- const defaults$l = {
5222
+ const defaults$m = {
5174
5223
  style: "uppercase",
5175
5224
  };
5176
5225
  class DoctypeStyle extends Rule {
5177
5226
  constructor(options) {
5178
- super({ ...defaults$l, ...options });
5227
+ super({ ...defaults$m, ...options });
5179
5228
  }
5180
5229
  static schema() {
5181
5230
  return {
@@ -5207,12 +5256,12 @@ class DoctypeStyle extends Rule {
5207
5256
  }
5208
5257
  }
5209
5258
 
5210
- const defaults$k = {
5259
+ const defaults$l = {
5211
5260
  style: "lowercase",
5212
5261
  };
5213
5262
  class ElementCase extends Rule {
5214
5263
  constructor(options) {
5215
- super({ ...defaults$k, ...options });
5264
+ super({ ...defaults$l, ...options });
5216
5265
  this.style = new rulesHelper.CaseStyle(this.options.style, "element-case");
5217
5266
  }
5218
5267
  static schema() {
@@ -5278,14 +5327,14 @@ class ElementCase extends Rule {
5278
5327
  }
5279
5328
  }
5280
5329
 
5281
- const defaults$j = {
5330
+ const defaults$k = {
5282
5331
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5283
5332
  whitelist: [],
5284
5333
  blacklist: [],
5285
5334
  };
5286
5335
  class ElementName extends Rule {
5287
5336
  constructor(options) {
5288
- super({ ...defaults$j, ...options });
5337
+ super({ ...defaults$k, ...options });
5289
5338
  // eslint-disable-next-line security/detect-non-literal-regexp
5290
5339
  this.pattern = new RegExp(this.options.pattern);
5291
5340
  }
@@ -5326,7 +5375,7 @@ class ElementName extends Rule {
5326
5375
  ...context.blacklist.map((cur) => `- ${cur}`),
5327
5376
  ];
5328
5377
  }
5329
- if (context.pattern !== defaults$j.pattern) {
5378
+ if (context.pattern !== defaults$k.pattern) {
5330
5379
  return [
5331
5380
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5332
5381
  "",
@@ -5517,24 +5566,16 @@ class ElementPermittedOccurrences extends Rule {
5517
5566
  this.on("dom:ready", (event) => {
5518
5567
  const doc = event.document;
5519
5568
  doc.visitDepthFirst((node) => {
5520
- const parent = node.parent;
5521
- if (!parent || !parent.meta) {
5569
+ if (!node || !node.meta) {
5522
5570
  return;
5523
5571
  }
5524
- const rules = parent.meta.permittedContent;
5572
+ const rules = node.meta.permittedContent;
5525
5573
  if (!rules) {
5526
5574
  return;
5527
5575
  }
5528
- const siblings = parent.childElements.filter((cur) => cur.tagName === node.tagName);
5529
- const first = node.unique === siblings[0].unique;
5530
- /* the first occurrence should not trigger any errors, only the
5531
- * subsequent occurrences should. */
5532
- if (first) {
5533
- return;
5534
- }
5535
- if (parent.meta && !Validator.validateOccurrences(node, rules, siblings.length)) {
5536
- this.report(node, `Element <${node.tagName}> can only appear once under ${parent.annotatedName}`);
5537
- }
5576
+ Validator.validateOccurrences(node.childElements, rules, (child, category) => {
5577
+ this.report(child, `Element <${category}> can only appear once under ${node.annotatedName}`);
5578
+ });
5538
5579
  });
5539
5580
  });
5540
5581
  }
@@ -5569,11 +5610,11 @@ class ElementPermittedOrder extends Rule {
5569
5610
  function isCategoryOrTag(value) {
5570
5611
  return typeof value === "string";
5571
5612
  }
5572
- function isCategory(value) {
5613
+ function isCategory$1(value) {
5573
5614
  return value[0] === "@";
5574
5615
  }
5575
5616
  function formatCategoryOrTag(value) {
5576
- return isCategory(value) ? value.slice(1) : `<${value}>`;
5617
+ return isCategory$1(value) ? value.slice(1) : `<${value}>`;
5577
5618
  }
5578
5619
  function isFormattable(rules) {
5579
5620
  return rules.length > 0 && rules.every(isCategoryOrTag);
@@ -5586,7 +5627,7 @@ function getRuleDescription$1(context) {
5586
5627
  const preamble = `The \`${child}\` element cannot have a \`${parent}\` element as parent.`;
5587
5628
  if (isFormattable(rules)) {
5588
5629
  const allowed = rules.filter(isCategoryOrTag).map((it) => {
5589
- if (isCategory(it)) {
5630
+ if (isCategory$1(it)) {
5590
5631
  return `- any ${it.slice(1)} element`;
5591
5632
  }
5592
5633
  else {
@@ -5749,6 +5790,9 @@ class ElementRequiredAttributes extends Rule {
5749
5790
  }
5750
5791
  }
5751
5792
 
5793
+ function isCategory(value) {
5794
+ return value[0] === "@";
5795
+ }
5752
5796
  class ElementRequiredContent extends Rule {
5753
5797
  documentation(context) {
5754
5798
  if (context) {
@@ -5783,7 +5827,8 @@ class ElementRequiredContent extends Rule {
5783
5827
  element: node.annotatedName,
5784
5828
  missing: `<${missing}>`,
5785
5829
  };
5786
- const message = `${node.annotatedName} element must have <${missing}> as content`;
5830
+ const tag = isCategory(missing) ? `${missing.slice(1)} element` : `<${missing}>`;
5831
+ const message = `${node.annotatedName} element must have ${tag} as content`;
5787
5832
  this.report(node, message, null, context);
5788
5833
  }
5789
5834
  });
@@ -5874,7 +5919,7 @@ class EmptyTitle extends Rule {
5874
5919
  }
5875
5920
  }
5876
5921
 
5877
- const defaults$i = {
5922
+ const defaults$j = {
5878
5923
  allowMultipleH1: false,
5879
5924
  minInitialRank: "h1",
5880
5925
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5905,7 +5950,7 @@ function parseMaxInitial(value) {
5905
5950
  }
5906
5951
  class HeadingLevel extends Rule {
5907
5952
  constructor(options) {
5908
- super({ ...defaults$i, ...options });
5953
+ super({ ...defaults$j, ...options });
5909
5954
  this.stack = [];
5910
5955
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5911
5956
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -6063,12 +6108,12 @@ class HeadingLevel extends Rule {
6063
6108
  }
6064
6109
  }
6065
6110
 
6066
- const defaults$h = {
6111
+ const defaults$i = {
6067
6112
  pattern: "kebabcase",
6068
6113
  };
6069
6114
  class IdPattern extends Rule {
6070
6115
  constructor(options) {
6071
- super({ ...defaults$h, ...options });
6116
+ super({ ...defaults$i, ...options });
6072
6117
  this.pattern = parsePattern(this.options.pattern);
6073
6118
  }
6074
6119
  static schema() {
@@ -6350,12 +6395,12 @@ function findLabelByParent(el) {
6350
6395
  return [];
6351
6396
  }
6352
6397
 
6353
- const defaults$g = {
6398
+ const defaults$h = {
6354
6399
  maxlength: 70,
6355
6400
  };
6356
6401
  class LongTitle extends Rule {
6357
6402
  constructor(options) {
6358
- super({ ...defaults$g, ...options });
6403
+ super({ ...defaults$h, ...options });
6359
6404
  this.maxlength = this.options.maxlength;
6360
6405
  }
6361
6406
  static schema() {
@@ -6545,13 +6590,13 @@ class MultipleLabeledControls extends Rule {
6545
6590
  }
6546
6591
  }
6547
6592
 
6548
- const defaults$f = {
6593
+ const defaults$g = {
6549
6594
  include: null,
6550
6595
  exclude: null,
6551
6596
  };
6552
6597
  class NoAutoplay extends Rule {
6553
6598
  constructor(options) {
6554
- super({ ...defaults$f, ...options });
6599
+ super({ ...defaults$g, ...options });
6555
6600
  }
6556
6601
  documentation(context) {
6557
6602
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6792,14 +6837,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6792
6837
  }
6793
6838
  }
6794
6839
 
6795
- const defaults$e = {
6840
+ const defaults$f = {
6796
6841
  include: null,
6797
6842
  exclude: null,
6798
6843
  allowedProperties: ["display"],
6799
6844
  };
6800
6845
  class NoInlineStyle extends Rule {
6801
6846
  constructor(options) {
6802
- super({ ...defaults$e, ...options });
6847
+ super({ ...defaults$f, ...options });
6803
6848
  }
6804
6849
  static schema() {
6805
6850
  return {
@@ -7001,7 +7046,7 @@ class NoMultipleMain extends Rule {
7001
7046
  }
7002
7047
  }
7003
7048
 
7004
- const defaults$d = {
7049
+ const defaults$e = {
7005
7050
  relaxed: false,
7006
7051
  };
7007
7052
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -7018,7 +7063,7 @@ const replacementTable = {
7018
7063
  };
7019
7064
  class NoRawCharacters extends Rule {
7020
7065
  constructor(options) {
7021
- super({ ...defaults$d, ...options });
7066
+ super({ ...defaults$e, ...options });
7022
7067
  this.relaxed = this.options.relaxed;
7023
7068
  }
7024
7069
  static schema() {
@@ -7196,13 +7241,13 @@ class NoRedundantRole extends Rule {
7196
7241
  }
7197
7242
 
7198
7243
  const xmlns = /^(.+):.+$/;
7199
- const defaults$c = {
7244
+ const defaults$d = {
7200
7245
  ignoreForeign: true,
7201
7246
  ignoreXML: true,
7202
7247
  };
7203
7248
  class NoSelfClosing extends Rule {
7204
7249
  constructor(options) {
7205
- super({ ...defaults$c, ...options });
7250
+ super({ ...defaults$d, ...options });
7206
7251
  }
7207
7252
  static schema() {
7208
7253
  return {
@@ -7291,7 +7336,44 @@ class NoTrailingWhitespace extends Rule {
7291
7336
  }
7292
7337
  }
7293
7338
 
7339
+ const defaults$c = {
7340
+ include: null,
7341
+ exclude: null,
7342
+ };
7294
7343
  class NoUnknownElements extends Rule {
7344
+ constructor(options) {
7345
+ super({ ...defaults$c, ...options });
7346
+ }
7347
+ static schema() {
7348
+ return {
7349
+ exclude: {
7350
+ anyOf: [
7351
+ {
7352
+ items: {
7353
+ type: "string",
7354
+ },
7355
+ type: "array",
7356
+ },
7357
+ {
7358
+ type: "null",
7359
+ },
7360
+ ],
7361
+ },
7362
+ include: {
7363
+ anyOf: [
7364
+ {
7365
+ items: {
7366
+ type: "string",
7367
+ },
7368
+ type: "array",
7369
+ },
7370
+ {
7371
+ type: "null",
7372
+ },
7373
+ ],
7374
+ },
7375
+ };
7376
+ }
7295
7377
  documentation(context) {
7296
7378
  const element = context ? ` <${context}>` : "";
7297
7379
  return {
@@ -7302,9 +7384,13 @@ class NoUnknownElements extends Rule {
7302
7384
  setup() {
7303
7385
  this.on("tag:start", (event) => {
7304
7386
  const node = event.target;
7305
- if (!node.meta) {
7306
- this.report(node, `Unknown element <${node.tagName}>`, null, node.tagName);
7387
+ if (node.meta) {
7388
+ return;
7307
7389
  }
7390
+ if (this.isKeywordIgnored(node.tagName, rulesHelper.keywordPatternMatcher)) {
7391
+ return;
7392
+ }
7393
+ this.report(node, `Unknown element <${node.tagName}>`, null, node.tagName);
7308
7394
  });
7309
7395
  }
7310
7396
  }
@@ -8766,6 +8852,11 @@ class H37 extends Rule {
8766
8852
  }
8767
8853
  }
8768
8854
 
8855
+ var _a;
8856
+ /* istanbul ignore next: this will always be present for the <th>
8857
+ * attribute (or the tests would fail) */
8858
+ const { enum: validScopes } = (_a = elements.html5.th.attributes) === null || _a === void 0 ? void 0 : _a.scope;
8859
+ const joinedScopes = rulesHelper.naturalJoin(validScopes);
8769
8860
  class H63 extends Rule {
8770
8861
  documentation() {
8771
8862
  return {
@@ -8775,19 +8866,25 @@ class H63 extends Rule {
8775
8866
  }
8776
8867
  setup() {
8777
8868
  this.on("tag:ready", (event) => {
8778
- var _a, _b, _c, _d;
8869
+ var _a, _b;
8779
8870
  const node = event.target;
8780
8871
  /* only validate th */
8781
8872
  if (!node || node.tagName !== "th") {
8782
8873
  return;
8783
8874
  }
8875
+ const scope = node.getAttribute("scope");
8876
+ const value = scope === null || scope === void 0 ? void 0 : scope.value;
8877
+ /* ignore dynamic scope */
8878
+ if (value instanceof DynamicValue) {
8879
+ return;
8880
+ }
8784
8881
  /* ignore elements with valid scope values */
8785
- const scope = node.getAttributeValue("scope");
8786
- const scopeMeta = (_b = (_a = elements.html5 === null || elements.html5 === void 0 ? void 0 : elements.html5.th) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b.scope;
8787
- if (scope && ((_c = scopeMeta.enum) === null || _c === void 0 ? void 0 : _c.includes(scope))) {
8882
+ if (value && validScopes.includes(value)) {
8788
8883
  return;
8789
8884
  }
8790
- this.report(node, `<th> element must have a valid scope attribute: ${((_d = scopeMeta.enum) !== null && _d !== void 0 ? _d : []).join(", ")}`, node.location);
8885
+ const message = `<th> element must have a valid scope attribute: ${joinedScopes}`;
8886
+ const location = (_b = (_a = scope === null || scope === void 0 ? void 0 : scope.valueLocation) !== null && _a !== void 0 ? _a : scope === null || scope === void 0 ? void 0 : scope.keyLocation) !== null && _b !== void 0 ? _b : node.location;
8887
+ this.report(node, message, location);
8791
8888
  });
8792
8889
  }
8793
8890
  }
@@ -11158,7 +11255,7 @@ class HtmlValidate {
11158
11255
  /** @public */
11159
11256
  const name = "html-validate";
11160
11257
  /** @public */
11161
- const version = "7.10.1";
11258
+ const version = "7.11.1";
11162
11259
  /** @public */
11163
11260
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11164
11261