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/es/core.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import fs from 'fs';
2
2
  import betterAjvErrors from '@sidvind/better-ajv-errors';
3
- import { C as CaseStyle, n as naturalJoin, c as classifyNodeText, T as TextClassification, h as hasAltText, i as isHTMLHidden, a as isAriaHidden, b as hasAccessibleName, d as inAccessibilityTree, e as hasAriaLabel } from './rules-helper.js';
3
+ import { i as isKeywordIgnored, C as CaseStyle, n as naturalJoin, c as classifyNodeText, T as TextClassification, h as hasAltText, a as isHTMLHidden, b as isAriaHidden, d as hasAccessibleName, k as keywordPatternMatcher, e as inAccessibilityTree, f as hasAriaLabel } from './rules-helper.js';
4
4
  import Ajv from 'ajv';
5
5
  import deepmerge from 'deepmerge';
6
6
  import * as espree from 'espree';
7
7
  import * as walk from 'acorn-walk';
8
- import { e as entities$1, h as html5, b as bundledElements } from './elements.js';
9
8
  import path from 'path';
10
9
  import semver from 'semver';
11
10
  import kleur from 'kleur';
11
+ import { e as entities$1, h as html5, b as bundledElements } from './elements.js';
12
12
  import { createRequire } from 'module';
13
13
  import { codeFrameColumns } from '@babel/code-frame';
14
14
  import stylishImpl from '@html-validate/stylish';
@@ -1640,6 +1640,7 @@ class NestedError extends Error {
1640
1640
  constructor(message, nested) {
1641
1641
  super(message);
1642
1642
  Error.captureStackTrace(this, NestedError);
1643
+ this.name = NestedError.name;
1643
1644
  if (nested && nested.stack) {
1644
1645
  this.stack += `\nCaused by: ${nested.stack}`;
1645
1646
  }
@@ -1650,6 +1651,44 @@ class NestedError extends Error {
1650
1651
  * @public
1651
1652
  */
1652
1653
  class UserError extends NestedError {
1654
+ constructor(message, nested) {
1655
+ super(message, nested);
1656
+ Error.captureStackTrace(this, UserError);
1657
+ this.name = UserError.name;
1658
+ }
1659
+ prettyFormat() {
1660
+ return undefined;
1661
+ }
1662
+ }
1663
+
1664
+ /**
1665
+ * @internal
1666
+ */
1667
+ class InheritError extends UserError {
1668
+ constructor({ tagName, inherit }) {
1669
+ const message = `Element <${tagName}> cannot inherit from <${inherit}>: no such element`;
1670
+ super(message);
1671
+ Error.captureStackTrace(this, InheritError);
1672
+ this.name = InheritError.name;
1673
+ this.tagName = tagName;
1674
+ this.inherit = inherit;
1675
+ this.filename = null;
1676
+ }
1677
+ prettyFormat() {
1678
+ const { message, tagName, inherit } = this;
1679
+ const source = this.filename
1680
+ ? ["", "This error occurred when loading element metadata from:", `"${this.filename}"`, ""]
1681
+ : [""];
1682
+ return [
1683
+ message,
1684
+ ...source,
1685
+ "This usually occurs when the elements are defined in the wrong order, try one of the following:",
1686
+ "",
1687
+ ` - Ensure the spelling of "${inherit}" is correct.`,
1688
+ ` - Ensure the file containing "${inherit}" is loaded before the file containing "${tagName}".`,
1689
+ ` - Move the definition of "${inherit}" above the definition for "${tagName}".`,
1690
+ ].join("\n");
1691
+ }
1653
1692
  }
1654
1693
 
1655
1694
  function getSummary(schema, obj, errors) {
@@ -2073,6 +2112,9 @@ const definitions = {
2073
2112
  items: {
2074
2113
  type: "string"
2075
2114
  }
2115
+ },
2116
+ {
2117
+ type: "null"
2076
2118
  }
2077
2119
  ]
2078
2120
  }
@@ -2383,6 +2425,10 @@ class MetaTable {
2383
2425
  this.loadFromObject(data, filename);
2384
2426
  }
2385
2427
  catch (err) {
2428
+ if (err instanceof InheritError) {
2429
+ err.filename = filename;
2430
+ throw err;
2431
+ }
2386
2432
  if (err instanceof SchemaValidationError) {
2387
2433
  throw err;
2388
2434
  }
@@ -2435,7 +2481,10 @@ class MetaTable {
2435
2481
  const name = entry.inherit;
2436
2482
  parent = this.elements[name];
2437
2483
  if (!parent) {
2438
- throw new UserError(`Element <${tagName}> cannot inherit from <${name}>: no such element`);
2484
+ throw new InheritError({
2485
+ tagName,
2486
+ inherit: name,
2487
+ });
2439
2488
  }
2440
2489
  }
2441
2490
  /* merge all sources together */
@@ -2614,29 +2663,40 @@ class Validator {
2614
2663
  *
2615
2664
  * For instance, a `<table>` element can only contain a single `<tbody>`
2616
2665
  * child. If multiple `<tbody>` exists this test will fail both nodes.
2666
+ * Note that this is called on the parent but will fail the children violating
2667
+ * the rule.
2617
2668
  *
2618
- * @param node - Element to test.
2619
- * @param rules - List of rules.
2620
- * @param numSiblings - How many siblings of the same type as the element
2621
- * exists (including the element itself)
2622
- * @returns `true` if the element passes the test.
2669
+ * @param children - Array of children to validate.
2670
+ * @param rules - List of rules of the parent element.
2671
+ * @returns `true` if the parent element of the children passes the test.
2623
2672
  */
2624
- static validateOccurrences(node, rules, numSiblings) {
2673
+ static validateOccurrences(children, rules, cb) {
2625
2674
  if (!rules) {
2626
2675
  return true;
2627
2676
  }
2628
- const category = rules.find((cur) => {
2677
+ let valid = true;
2678
+ for (const rule of rules) {
2629
2679
  /** @todo handle complex rules and not just plain arrays (but as of now
2630
2680
  * there is no use-case for it) */
2631
2681
  // istanbul ignore next
2632
- if (typeof cur !== "string") {
2682
+ if (typeof rule !== "string") {
2633
2683
  return false;
2634
2684
  }
2635
- const match = cur.match(/^(.*?)[?*]?$/);
2636
- return match && match[1] === node.tagName;
2637
- });
2638
- const limit = parseAmountQualifier(category);
2639
- return limit === null || numSiblings <= limit;
2685
+ // Check if the rule has a quantifier
2686
+ const [, category, quantifier] = rule.match(/^(@?.*?)([?*]?)$/);
2687
+ const limit = category && quantifier && parseQuantifier(quantifier);
2688
+ if (limit) {
2689
+ const siblings = children.filter((cur) => Validator.validatePermittedCategory(cur, rule, true));
2690
+ if (siblings.length > limit) {
2691
+ // fail only the children above the limit (currently limit can only be 1)
2692
+ for (const child of siblings.slice(limit)) {
2693
+ cb(child, category);
2694
+ }
2695
+ valid = false;
2696
+ }
2697
+ }
2698
+ }
2699
+ return valid;
2640
2700
  }
2641
2701
  /**
2642
2702
  * Validate elements order.
@@ -2698,14 +2758,15 @@ class Validator {
2698
2758
  * Check if an element has the required set of elements. At least one of the
2699
2759
  * selectors must match.
2700
2760
  *
2701
- * Returns [] when valid or a list of tagNames missing as content.
2761
+ * Returns `[]` when valid or a list of required but missing tagnames or
2762
+ * categories.
2702
2763
  */
2703
2764
  static validateRequiredContent(node, rules) {
2704
2765
  if (!rules || rules.length === 0) {
2705
2766
  return [];
2706
2767
  }
2707
2768
  return rules.filter((tagName) => {
2708
- const haveMatchingChild = node.childElements.some((child) => child.is(tagName));
2769
+ const haveMatchingChild = node.childElements.some((child) => Validator.validatePermittedCategory(child, tagName, false));
2709
2770
  return !haveMatchingChild;
2710
2771
  });
2711
2772
  }
@@ -2809,16 +2870,16 @@ class Validator {
2809
2870
  */
2810
2871
  // eslint-disable-next-line complexity
2811
2872
  static validatePermittedCategory(node, category, defaultMatch) {
2873
+ const [, rawCategory] = category.match(/^(@?.*?)([?*]?)$/);
2812
2874
  /* match tagName when an explicit name is given */
2813
- if (category[0] !== "@") {
2814
- const [, tagName] = category.match(/^(.*?)[?*]?$/);
2815
- return node.tagName === tagName;
2875
+ if (rawCategory[0] !== "@") {
2876
+ return node.tagName === rawCategory;
2816
2877
  }
2817
2878
  /* if the meta entry is missing assume any content model would match */
2818
2879
  if (!node.meta) {
2819
2880
  return defaultMatch;
2820
2881
  }
2821
- switch (category) {
2882
+ switch (rawCategory) {
2822
2883
  case "@meta":
2823
2884
  return node.meta.metadata;
2824
2885
  case "@flow":
@@ -2850,23 +2911,15 @@ function validateKeys(rule) {
2850
2911
  }
2851
2912
  }
2852
2913
  }
2853
- function parseAmountQualifier(category) {
2854
- if (!category) {
2855
- /* content not allowed, catched by another rule so just assume unlimited
2856
- * usage for this purpose */
2857
- return null;
2858
- }
2859
- const [, qualifier] = category.match(/^.*?([?*]?)$/);
2860
- switch (qualifier) {
2914
+ function parseQuantifier(quantifier) {
2915
+ switch (quantifier) {
2861
2916
  case "?":
2862
2917
  return 1;
2863
- case "":
2864
- return null;
2865
2918
  case "*":
2866
2919
  return null;
2867
- /* istanbul ignore next */
2920
+ // istanbul ignore next
2868
2921
  default:
2869
- throw new Error(`Invalid amount qualifier "${qualifier}" used`);
2922
+ throw new Error(`Invalid quantifier "${quantifier}" used`);
2870
2923
  }
2871
2924
  }
2872
2925
 
@@ -3398,16 +3451,7 @@ class Rule {
3398
3451
  * `exclude`.
3399
3452
  */
3400
3453
  isKeywordIgnored(keyword, matcher = (list, it) => list.includes(it)) {
3401
- const { include, exclude } = this.options;
3402
- /* ignore keyword if not present in "include" */
3403
- if (include && !matcher(include, keyword)) {
3404
- return true;
3405
- }
3406
- /* ignore keyword if present in "excludes" */
3407
- if (exclude && matcher(exclude, keyword)) {
3408
- return true;
3409
- }
3410
- return false;
3454
+ return isKeywordIgnored(this.options, keyword, matcher);
3411
3455
  }
3412
3456
  /**
3413
3457
  * Find all tags which has enabled given property.
@@ -3533,7 +3577,7 @@ class Rule {
3533
3577
  }
3534
3578
  }
3535
3579
 
3536
- const defaults$u = {
3580
+ const defaults$v = {
3537
3581
  allowExternal: true,
3538
3582
  allowRelative: true,
3539
3583
  allowAbsolute: true,
@@ -3577,7 +3621,7 @@ function matchList(value, list) {
3577
3621
  }
3578
3622
  class AllowedLinks extends Rule {
3579
3623
  constructor(options) {
3580
- super({ ...defaults$u, ...options });
3624
+ super({ ...defaults$v, ...options });
3581
3625
  this.allowExternal = parseAllow(this.options.allowExternal);
3582
3626
  this.allowRelative = parseAllow(this.options.allowRelative);
3583
3627
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3725,7 +3769,7 @@ var RuleContext$1;
3725
3769
  RuleContext["MISSING_ALT"] = "missing-alt";
3726
3770
  RuleContext["MISSING_HREF"] = "missing-href";
3727
3771
  })(RuleContext$1 || (RuleContext$1 = {}));
3728
- const defaults$t = {
3772
+ const defaults$u = {
3729
3773
  accessible: true,
3730
3774
  };
3731
3775
  function findByTarget(target, siblings) {
@@ -3763,7 +3807,7 @@ function getDescription$1(context) {
3763
3807
  }
3764
3808
  class AreaAlt extends Rule {
3765
3809
  constructor(options) {
3766
- super({ ...defaults$t, ...options });
3810
+ super({ ...defaults$u, ...options });
3767
3811
  }
3768
3812
  static schema() {
3769
3813
  return {
@@ -3925,15 +3969,20 @@ class AriaLabelMisuse extends Rule {
3925
3969
  * @public
3926
3970
  */
3927
3971
  class ConfigError extends UserError {
3972
+ constructor(message, nested) {
3973
+ super(message, nested);
3974
+ Error.captureStackTrace(this, ConfigError);
3975
+ this.name = ConfigError.name;
3976
+ }
3928
3977
  }
3929
3978
 
3930
- const defaults$s = {
3979
+ const defaults$t = {
3931
3980
  style: "lowercase",
3932
3981
  ignoreForeign: true,
3933
3982
  };
3934
3983
  class AttrCase extends Rule {
3935
3984
  constructor(options) {
3936
- super({ ...defaults$s, ...options });
3985
+ super({ ...defaults$t, ...options });
3937
3986
  this.style = new CaseStyle(this.options.style, "attr-case");
3938
3987
  }
3939
3988
  static schema() {
@@ -4278,7 +4327,7 @@ class AttrDelimiter extends Rule {
4278
4327
  }
4279
4328
 
4280
4329
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4281
- const defaults$r = {
4330
+ const defaults$s = {
4282
4331
  pattern: DEFAULT_PATTERN,
4283
4332
  ignoreForeign: true,
4284
4333
  };
@@ -4315,7 +4364,7 @@ function generateDescription(name, pattern) {
4315
4364
  }
4316
4365
  class AttrPattern extends Rule {
4317
4366
  constructor(options) {
4318
- super({ ...defaults$r, ...options });
4367
+ super({ ...defaults$s, ...options });
4319
4368
  this.pattern = generateRegexp(this.options.pattern);
4320
4369
  }
4321
4370
  static schema() {
@@ -4376,7 +4425,7 @@ var QuoteStyle;
4376
4425
  QuoteStyle["AUTO_QUOTE"] = "auto";
4377
4426
  QuoteStyle["ANY_QUOTE"] = "any";
4378
4427
  })(QuoteStyle || (QuoteStyle = {}));
4379
- const defaults$q = {
4428
+ const defaults$r = {
4380
4429
  style: "auto",
4381
4430
  unquoted: false,
4382
4431
  };
@@ -4443,7 +4492,7 @@ class AttrQuotes extends Rule {
4443
4492
  };
4444
4493
  }
4445
4494
  constructor(options) {
4446
- super({ ...defaults$q, ...options });
4495
+ super({ ...defaults$r, ...options });
4447
4496
  this.style = parseStyle$4(this.options.style);
4448
4497
  }
4449
4498
  setup() {
@@ -4613,12 +4662,12 @@ class AttributeAllowedValues extends Rule {
4613
4662
  }
4614
4663
  }
4615
4664
 
4616
- const defaults$p = {
4665
+ const defaults$q = {
4617
4666
  style: "omit",
4618
4667
  };
4619
4668
  class AttributeBooleanStyle extends Rule {
4620
4669
  constructor(options) {
4621
- super({ ...defaults$p, ...options });
4670
+ super({ ...defaults$q, ...options });
4622
4671
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4623
4672
  }
4624
4673
  static schema() {
@@ -4694,12 +4743,12 @@ function reportMessage$1(attr, style) {
4694
4743
  return "";
4695
4744
  }
4696
4745
 
4697
- const defaults$o = {
4746
+ const defaults$p = {
4698
4747
  style: "omit",
4699
4748
  };
4700
4749
  class AttributeEmptyStyle extends Rule {
4701
4750
  constructor(options) {
4702
- super({ ...defaults$o, ...options });
4751
+ super({ ...defaults$p, ...options });
4703
4752
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4704
4753
  }
4705
4754
  static schema() {
@@ -4855,12 +4904,12 @@ function describePattern(pattern) {
4855
4904
  }
4856
4905
  }
4857
4906
 
4858
- const defaults$n = {
4907
+ const defaults$o = {
4859
4908
  pattern: "kebabcase",
4860
4909
  };
4861
4910
  class ClassPattern extends Rule {
4862
4911
  constructor(options) {
4863
- super({ ...defaults$n, ...options });
4912
+ super({ ...defaults$o, ...options });
4864
4913
  this.pattern = parsePattern(this.options.pattern);
4865
4914
  }
4866
4915
  static schema() {
@@ -4969,13 +5018,13 @@ class CloseOrder extends Rule {
4969
5018
  }
4970
5019
  }
4971
5020
 
4972
- const defaults$m = {
5021
+ const defaults$n = {
4973
5022
  include: null,
4974
5023
  exclude: null,
4975
5024
  };
4976
5025
  class Deprecated extends Rule {
4977
5026
  constructor(options) {
4978
- super({ ...defaults$m, ...options });
5027
+ super({ ...defaults$n, ...options });
4979
5028
  }
4980
5029
  static schema() {
4981
5030
  return {
@@ -5138,12 +5187,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5138
5187
  }
5139
5188
  };
5140
5189
 
5141
- const defaults$l = {
5190
+ const defaults$m = {
5142
5191
  style: "uppercase",
5143
5192
  };
5144
5193
  class DoctypeStyle extends Rule {
5145
5194
  constructor(options) {
5146
- super({ ...defaults$l, ...options });
5195
+ super({ ...defaults$m, ...options });
5147
5196
  }
5148
5197
  static schema() {
5149
5198
  return {
@@ -5175,12 +5224,12 @@ class DoctypeStyle extends Rule {
5175
5224
  }
5176
5225
  }
5177
5226
 
5178
- const defaults$k = {
5227
+ const defaults$l = {
5179
5228
  style: "lowercase",
5180
5229
  };
5181
5230
  class ElementCase extends Rule {
5182
5231
  constructor(options) {
5183
- super({ ...defaults$k, ...options });
5232
+ super({ ...defaults$l, ...options });
5184
5233
  this.style = new CaseStyle(this.options.style, "element-case");
5185
5234
  }
5186
5235
  static schema() {
@@ -5246,14 +5295,14 @@ class ElementCase extends Rule {
5246
5295
  }
5247
5296
  }
5248
5297
 
5249
- const defaults$j = {
5298
+ const defaults$k = {
5250
5299
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5251
5300
  whitelist: [],
5252
5301
  blacklist: [],
5253
5302
  };
5254
5303
  class ElementName extends Rule {
5255
5304
  constructor(options) {
5256
- super({ ...defaults$j, ...options });
5305
+ super({ ...defaults$k, ...options });
5257
5306
  // eslint-disable-next-line security/detect-non-literal-regexp
5258
5307
  this.pattern = new RegExp(this.options.pattern);
5259
5308
  }
@@ -5294,7 +5343,7 @@ class ElementName extends Rule {
5294
5343
  ...context.blacklist.map((cur) => `- ${cur}`),
5295
5344
  ];
5296
5345
  }
5297
- if (context.pattern !== defaults$j.pattern) {
5346
+ if (context.pattern !== defaults$k.pattern) {
5298
5347
  return [
5299
5348
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5300
5349
  "",
@@ -5485,24 +5534,16 @@ class ElementPermittedOccurrences extends Rule {
5485
5534
  this.on("dom:ready", (event) => {
5486
5535
  const doc = event.document;
5487
5536
  doc.visitDepthFirst((node) => {
5488
- const parent = node.parent;
5489
- if (!parent || !parent.meta) {
5537
+ if (!node || !node.meta) {
5490
5538
  return;
5491
5539
  }
5492
- const rules = parent.meta.permittedContent;
5540
+ const rules = node.meta.permittedContent;
5493
5541
  if (!rules) {
5494
5542
  return;
5495
5543
  }
5496
- const siblings = parent.childElements.filter((cur) => cur.tagName === node.tagName);
5497
- const first = node.unique === siblings[0].unique;
5498
- /* the first occurrence should not trigger any errors, only the
5499
- * subsequent occurrences should. */
5500
- if (first) {
5501
- return;
5502
- }
5503
- if (parent.meta && !Validator.validateOccurrences(node, rules, siblings.length)) {
5504
- this.report(node, `Element <${node.tagName}> can only appear once under ${parent.annotatedName}`);
5505
- }
5544
+ Validator.validateOccurrences(node.childElements, rules, (child, category) => {
5545
+ this.report(child, `Element <${category}> can only appear once under ${node.annotatedName}`);
5546
+ });
5506
5547
  });
5507
5548
  });
5508
5549
  }
@@ -5537,11 +5578,11 @@ class ElementPermittedOrder extends Rule {
5537
5578
  function isCategoryOrTag(value) {
5538
5579
  return typeof value === "string";
5539
5580
  }
5540
- function isCategory(value) {
5581
+ function isCategory$1(value) {
5541
5582
  return value[0] === "@";
5542
5583
  }
5543
5584
  function formatCategoryOrTag(value) {
5544
- return isCategory(value) ? value.slice(1) : `<${value}>`;
5585
+ return isCategory$1(value) ? value.slice(1) : `<${value}>`;
5545
5586
  }
5546
5587
  function isFormattable(rules) {
5547
5588
  return rules.length > 0 && rules.every(isCategoryOrTag);
@@ -5554,7 +5595,7 @@ function getRuleDescription$1(context) {
5554
5595
  const preamble = `The \`${child}\` element cannot have a \`${parent}\` element as parent.`;
5555
5596
  if (isFormattable(rules)) {
5556
5597
  const allowed = rules.filter(isCategoryOrTag).map((it) => {
5557
- if (isCategory(it)) {
5598
+ if (isCategory$1(it)) {
5558
5599
  return `- any ${it.slice(1)} element`;
5559
5600
  }
5560
5601
  else {
@@ -5717,6 +5758,9 @@ class ElementRequiredAttributes extends Rule {
5717
5758
  }
5718
5759
  }
5719
5760
 
5761
+ function isCategory(value) {
5762
+ return value[0] === "@";
5763
+ }
5720
5764
  class ElementRequiredContent extends Rule {
5721
5765
  documentation(context) {
5722
5766
  if (context) {
@@ -5751,7 +5795,8 @@ class ElementRequiredContent extends Rule {
5751
5795
  element: node.annotatedName,
5752
5796
  missing: `<${missing}>`,
5753
5797
  };
5754
- const message = `${node.annotatedName} element must have <${missing}> as content`;
5798
+ const tag = isCategory(missing) ? `${missing.slice(1)} element` : `<${missing}>`;
5799
+ const message = `${node.annotatedName} element must have ${tag} as content`;
5755
5800
  this.report(node, message, null, context);
5756
5801
  }
5757
5802
  });
@@ -5842,7 +5887,7 @@ class EmptyTitle extends Rule {
5842
5887
  }
5843
5888
  }
5844
5889
 
5845
- const defaults$i = {
5890
+ const defaults$j = {
5846
5891
  allowMultipleH1: false,
5847
5892
  minInitialRank: "h1",
5848
5893
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5873,7 +5918,7 @@ function parseMaxInitial(value) {
5873
5918
  }
5874
5919
  class HeadingLevel extends Rule {
5875
5920
  constructor(options) {
5876
- super({ ...defaults$i, ...options });
5921
+ super({ ...defaults$j, ...options });
5877
5922
  this.stack = [];
5878
5923
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5879
5924
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -6031,12 +6076,12 @@ class HeadingLevel extends Rule {
6031
6076
  }
6032
6077
  }
6033
6078
 
6034
- const defaults$h = {
6079
+ const defaults$i = {
6035
6080
  pattern: "kebabcase",
6036
6081
  };
6037
6082
  class IdPattern extends Rule {
6038
6083
  constructor(options) {
6039
- super({ ...defaults$h, ...options });
6084
+ super({ ...defaults$i, ...options });
6040
6085
  this.pattern = parsePattern(this.options.pattern);
6041
6086
  }
6042
6087
  static schema() {
@@ -6318,12 +6363,12 @@ function findLabelByParent(el) {
6318
6363
  return [];
6319
6364
  }
6320
6365
 
6321
- const defaults$g = {
6366
+ const defaults$h = {
6322
6367
  maxlength: 70,
6323
6368
  };
6324
6369
  class LongTitle extends Rule {
6325
6370
  constructor(options) {
6326
- super({ ...defaults$g, ...options });
6371
+ super({ ...defaults$h, ...options });
6327
6372
  this.maxlength = this.options.maxlength;
6328
6373
  }
6329
6374
  static schema() {
@@ -6513,13 +6558,13 @@ class MultipleLabeledControls extends Rule {
6513
6558
  }
6514
6559
  }
6515
6560
 
6516
- const defaults$f = {
6561
+ const defaults$g = {
6517
6562
  include: null,
6518
6563
  exclude: null,
6519
6564
  };
6520
6565
  class NoAutoplay extends Rule {
6521
6566
  constructor(options) {
6522
- super({ ...defaults$f, ...options });
6567
+ super({ ...defaults$g, ...options });
6523
6568
  }
6524
6569
  documentation(context) {
6525
6570
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6760,14 +6805,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6760
6805
  }
6761
6806
  }
6762
6807
 
6763
- const defaults$e = {
6808
+ const defaults$f = {
6764
6809
  include: null,
6765
6810
  exclude: null,
6766
6811
  allowedProperties: ["display"],
6767
6812
  };
6768
6813
  class NoInlineStyle extends Rule {
6769
6814
  constructor(options) {
6770
- super({ ...defaults$e, ...options });
6815
+ super({ ...defaults$f, ...options });
6771
6816
  }
6772
6817
  static schema() {
6773
6818
  return {
@@ -6969,7 +7014,7 @@ class NoMultipleMain extends Rule {
6969
7014
  }
6970
7015
  }
6971
7016
 
6972
- const defaults$d = {
7017
+ const defaults$e = {
6973
7018
  relaxed: false,
6974
7019
  };
6975
7020
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -6986,7 +7031,7 @@ const replacementTable = {
6986
7031
  };
6987
7032
  class NoRawCharacters extends Rule {
6988
7033
  constructor(options) {
6989
- super({ ...defaults$d, ...options });
7034
+ super({ ...defaults$e, ...options });
6990
7035
  this.relaxed = this.options.relaxed;
6991
7036
  }
6992
7037
  static schema() {
@@ -7164,13 +7209,13 @@ class NoRedundantRole extends Rule {
7164
7209
  }
7165
7210
 
7166
7211
  const xmlns = /^(.+):.+$/;
7167
- const defaults$c = {
7212
+ const defaults$d = {
7168
7213
  ignoreForeign: true,
7169
7214
  ignoreXML: true,
7170
7215
  };
7171
7216
  class NoSelfClosing extends Rule {
7172
7217
  constructor(options) {
7173
- super({ ...defaults$c, ...options });
7218
+ super({ ...defaults$d, ...options });
7174
7219
  }
7175
7220
  static schema() {
7176
7221
  return {
@@ -7259,7 +7304,44 @@ class NoTrailingWhitespace extends Rule {
7259
7304
  }
7260
7305
  }
7261
7306
 
7307
+ const defaults$c = {
7308
+ include: null,
7309
+ exclude: null,
7310
+ };
7262
7311
  class NoUnknownElements extends Rule {
7312
+ constructor(options) {
7313
+ super({ ...defaults$c, ...options });
7314
+ }
7315
+ static schema() {
7316
+ return {
7317
+ exclude: {
7318
+ anyOf: [
7319
+ {
7320
+ items: {
7321
+ type: "string",
7322
+ },
7323
+ type: "array",
7324
+ },
7325
+ {
7326
+ type: "null",
7327
+ },
7328
+ ],
7329
+ },
7330
+ include: {
7331
+ anyOf: [
7332
+ {
7333
+ items: {
7334
+ type: "string",
7335
+ },
7336
+ type: "array",
7337
+ },
7338
+ {
7339
+ type: "null",
7340
+ },
7341
+ ],
7342
+ },
7343
+ };
7344
+ }
7263
7345
  documentation(context) {
7264
7346
  const element = context ? ` <${context}>` : "";
7265
7347
  return {
@@ -7270,9 +7352,13 @@ class NoUnknownElements extends Rule {
7270
7352
  setup() {
7271
7353
  this.on("tag:start", (event) => {
7272
7354
  const node = event.target;
7273
- if (!node.meta) {
7274
- this.report(node, `Unknown element <${node.tagName}>`, null, node.tagName);
7355
+ if (node.meta) {
7356
+ return;
7275
7357
  }
7358
+ if (this.isKeywordIgnored(node.tagName, keywordPatternMatcher)) {
7359
+ return;
7360
+ }
7361
+ this.report(node, `Unknown element <${node.tagName}>`, null, node.tagName);
7276
7362
  });
7277
7363
  }
7278
7364
  }
@@ -8734,6 +8820,11 @@ class H37 extends Rule {
8734
8820
  }
8735
8821
  }
8736
8822
 
8823
+ var _a;
8824
+ /* istanbul ignore next: this will always be present for the <th>
8825
+ * attribute (or the tests would fail) */
8826
+ const { enum: validScopes } = (_a = html5.th.attributes) === null || _a === void 0 ? void 0 : _a.scope;
8827
+ const joinedScopes = naturalJoin(validScopes);
8737
8828
  class H63 extends Rule {
8738
8829
  documentation() {
8739
8830
  return {
@@ -8743,19 +8834,25 @@ class H63 extends Rule {
8743
8834
  }
8744
8835
  setup() {
8745
8836
  this.on("tag:ready", (event) => {
8746
- var _a, _b, _c, _d;
8837
+ var _a, _b;
8747
8838
  const node = event.target;
8748
8839
  /* only validate th */
8749
8840
  if (!node || node.tagName !== "th") {
8750
8841
  return;
8751
8842
  }
8843
+ const scope = node.getAttribute("scope");
8844
+ const value = scope === null || scope === void 0 ? void 0 : scope.value;
8845
+ /* ignore dynamic scope */
8846
+ if (value instanceof DynamicValue) {
8847
+ return;
8848
+ }
8752
8849
  /* ignore elements with valid scope values */
8753
- const scope = node.getAttributeValue("scope");
8754
- const scopeMeta = (_b = (_a = html5 === null || html5 === void 0 ? void 0 : html5.th) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b.scope;
8755
- if (scope && ((_c = scopeMeta.enum) === null || _c === void 0 ? void 0 : _c.includes(scope))) {
8850
+ if (value && validScopes.includes(value)) {
8756
8851
  return;
8757
8852
  }
8758
- this.report(node, `<th> element must have a valid scope attribute: ${((_d = scopeMeta.enum) !== null && _d !== void 0 ? _d : []).join(", ")}`, node.location);
8853
+ const message = `<th> element must have a valid scope attribute: ${joinedScopes}`;
8854
+ 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;
8855
+ this.report(node, message, location);
8759
8856
  });
8760
8857
  }
8761
8858
  }
@@ -11126,7 +11223,7 @@ class HtmlValidate {
11126
11223
  /** @public */
11127
11224
  const name = "html-validate";
11128
11225
  /** @public */
11129
- const version = "7.10.1";
11226
+ const version = "7.11.1";
11130
11227
  /** @public */
11131
11228
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11132
11229