html-validate 10.9.0 → 10.11.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 (38) hide show
  1. package/dist/cjs/cli.js +10 -10
  2. package/dist/cjs/cli.js.map +1 -1
  3. package/dist/cjs/core-browser.js +1 -1
  4. package/dist/cjs/core-browser.js.map +1 -1
  5. package/dist/cjs/core-nodejs.js +1 -1
  6. package/dist/cjs/core-nodejs.js.map +1 -1
  7. package/dist/cjs/core.js +286 -189
  8. package/dist/cjs/core.js.map +1 -1
  9. package/dist/cjs/elements.js +24 -16
  10. package/dist/cjs/elements.js.map +1 -1
  11. package/dist/cjs/html-validate.js +23 -15
  12. package/dist/cjs/html-validate.js.map +1 -1
  13. package/dist/cjs/matchers-jestonly.js +2 -2
  14. package/dist/cjs/matchers-jestonly.js.map +1 -1
  15. package/dist/cjs/tsdoc-metadata.json +1 -1
  16. package/dist/cjs/utils/natural-join.js +1 -1
  17. package/dist/cjs/utils/natural-join.js.map +1 -1
  18. package/dist/esm/cli.js +10 -10
  19. package/dist/esm/cli.js.map +1 -1
  20. package/dist/esm/core-browser.js +1 -1
  21. package/dist/esm/core-browser.js.map +1 -1
  22. package/dist/esm/core-nodejs.js +1 -1
  23. package/dist/esm/core-nodejs.js.map +1 -1
  24. package/dist/esm/core.js +286 -189
  25. package/dist/esm/core.js.map +1 -1
  26. package/dist/esm/elements.js +24 -16
  27. package/dist/esm/elements.js.map +1 -1
  28. package/dist/esm/html-validate.js +24 -16
  29. package/dist/esm/html-validate.js.map +1 -1
  30. package/dist/esm/matchers-jestonly.js +2 -2
  31. package/dist/esm/matchers-jestonly.js.map +1 -1
  32. package/dist/esm/utils/natural-join.js +1 -1
  33. package/dist/esm/utils/natural-join.js.map +1 -1
  34. package/dist/schema/elements.json +6 -0
  35. package/dist/tsdoc-metadata.json +1 -1
  36. package/dist/types/browser.d.ts +24 -9
  37. package/dist/types/index.d.ts +24 -9
  38. package/package.json +3 -3
package/dist/esm/core.js CHANGED
@@ -395,6 +395,7 @@ function stringify(value) {
395
395
  class WrappedError extends Error {
396
396
  constructor(message) {
397
397
  super(stringify(message));
398
+ this.name = "WrappedError";
398
399
  }
399
400
  }
400
401
 
@@ -409,8 +410,7 @@ function ensureError(value) {
409
410
  class NestedError extends Error {
410
411
  constructor(message, nested) {
411
412
  super(message);
412
- Error.captureStackTrace(this, NestedError);
413
- this.name = NestedError.name;
413
+ this.name = "NestedError";
414
414
  if (nested?.stack) {
415
415
  this.stack ??= "";
416
416
  this.stack += `
@@ -422,8 +422,7 @@ Caused by: ${nested.stack}`;
422
422
  class UserError extends NestedError {
423
423
  constructor(message, nested) {
424
424
  super(message, nested);
425
- Error.captureStackTrace(this, UserError);
426
- this.name = UserError.name;
425
+ this.name = "UserError";
427
426
  Object.defineProperty(this, "isUserError", {
428
427
  value: true,
429
428
  enumerable: false,
@@ -446,8 +445,7 @@ class InheritError extends UserError {
446
445
  constructor({ tagName, inherit }) {
447
446
  const message = `Element <${tagName}> cannot inherit from <${inherit}>: no such element`;
448
447
  super(message);
449
- Error.captureStackTrace(this, InheritError);
450
- this.name = InheritError.name;
448
+ this.name = "InheritError";
451
449
  this.tagName = tagName;
452
450
  this.inherit = inherit;
453
451
  this.filename = null;
@@ -489,6 +487,7 @@ class SchemaValidationError extends UserError {
489
487
  constructor(filename, message, obj, schema, errors) {
490
488
  const summary = getSummary(schema, obj, errors);
491
489
  super(`${message}: ${summary}`);
490
+ this.name = "SchemaValidationError";
492
491
  this.filename = filename;
493
492
  this.obj = obj;
494
493
  this.schema = schema;
@@ -636,6 +635,18 @@ const patternProperties = {
636
635
  }
637
636
  ]
638
637
  },
638
+ submitButton: {
639
+ title: "Mark this element as a submit button",
640
+ description: "This element can be used to submit forms.",
641
+ anyOf: [
642
+ {
643
+ type: "boolean"
644
+ },
645
+ {
646
+ "function": true
647
+ }
648
+ ]
649
+ },
639
650
  templateRoot: {
640
651
  title: "Mark element as an element ignoring DOM ancestry, i.e. <template>.",
641
652
  description: "The <template> element can contain any elements.",
@@ -1019,6 +1030,7 @@ const MetaCopyableProperty = [
1019
1030
  "form",
1020
1031
  "formAssociated",
1021
1032
  "labelable",
1033
+ "submitButton",
1022
1034
  "attributes",
1023
1035
  "aria",
1024
1036
  "permittedContent",
@@ -1033,7 +1045,7 @@ function setMetaProperty(dst, key, value) {
1033
1045
  }
1034
1046
 
1035
1047
  function isSet(value) {
1036
- return typeof value !== "undefined";
1048
+ return value !== void 0;
1037
1049
  }
1038
1050
  function flag(value) {
1039
1051
  return value ? true : void 0;
@@ -1048,7 +1060,7 @@ function migrateSingleAttribute(src, key) {
1048
1060
  result.required = flag(src.requiredAttributes?.includes(key));
1049
1061
  result.omit = void 0;
1050
1062
  const attr = src.attributes ? src.attributes[key] : void 0;
1051
- if (typeof attr === "undefined") {
1063
+ if (attr === void 0) {
1052
1064
  return stripUndefined(result);
1053
1065
  }
1054
1066
  if (attr === null) {
@@ -1075,7 +1087,7 @@ function migrateAttributes(src) {
1075
1087
  ...src.requiredAttributes ?? [],
1076
1088
  ...src.deprecatedAttributes ?? []
1077
1089
  /* eslint-disable-next-line sonarjs/no-alphabetical-sort -- not really needed in this case, this is a-z anyway */
1078
- ].sort();
1090
+ ].toSorted();
1079
1091
  const entries = keys.map((key) => {
1080
1092
  return [key, migrateSingleAttribute(src, key)];
1081
1093
  });
@@ -1103,9 +1115,7 @@ function migrateElement(src) {
1103
1115
  const implicitRole = normalizeAriaImplicitRole(src.implicitRole ?? src.aria?.implicitRole);
1104
1116
  const result = {
1105
1117
  ...src,
1106
- ...{
1107
- formAssociated: void 0
1108
- },
1118
+ formAssociated: void 0,
1109
1119
  attributes: migrateAttributes(src),
1110
1120
  textContent: src.textContent,
1111
1121
  focusable: src.focusable ?? false,
@@ -1140,7 +1150,8 @@ const dynamicKeys = [
1140
1150
  "phrasing",
1141
1151
  "embedded",
1142
1152
  "interactive",
1143
- "labelable"
1153
+ "labelable",
1154
+ "submitButton"
1144
1155
  ];
1145
1156
  const schemaCache = /* @__PURE__ */ new Map();
1146
1157
  function clone(src) {
@@ -1410,13 +1421,10 @@ class Attribute {
1410
1421
  */
1411
1422
  constructor(key, value, keyLocation, valueLocation, originalAttribute) {
1412
1423
  this.key = key;
1413
- this.value = value;
1424
+ this.value = value ?? null;
1414
1425
  this.keyLocation = keyLocation;
1415
1426
  this.valueLocation = valueLocation;
1416
1427
  this.originalAttribute = originalAttribute;
1417
- if (typeof this.value === "undefined") {
1418
- this.value = null;
1419
- }
1420
1428
  }
1421
1429
  /**
1422
1430
  * Flag set to true if the attribute value is static.
@@ -1560,11 +1568,11 @@ class Context {
1560
1568
  while ((offset = consumed.indexOf("\n")) >= 0) {
1561
1569
  this.line++;
1562
1570
  this.column = 1;
1563
- consumed = consumed.substr(offset + 1);
1571
+ consumed = consumed.slice(offset + 1);
1564
1572
  }
1565
1573
  this.column += consumed.length;
1566
1574
  this.offset += n;
1567
- this.string = this.string.substr(n);
1575
+ this.string = this.string.slice(n);
1568
1576
  this.state = state;
1569
1577
  }
1570
1578
  getLocation(size) {
@@ -1742,7 +1750,7 @@ class DOMNode {
1742
1750
  * node has no children.
1743
1751
  */
1744
1752
  get lastChild() {
1745
- return this.childNodes[this.childNodes.length - 1] || null;
1753
+ return this.childNodes.at(-1) ?? null;
1746
1754
  }
1747
1755
  /**
1748
1756
  * @internal
@@ -1836,7 +1844,7 @@ class DOMNode {
1836
1844
  }
1837
1845
  _removeChild(node) {
1838
1846
  const index = this.childNodes.findIndex((it) => it.isSameNode(node));
1839
- if (index >= 0) {
1847
+ if (index !== -1) {
1840
1848
  this.childNodes.splice(index, 1);
1841
1849
  } else {
1842
1850
  throw new Error("DOMException: _removeChild(..) could not find child to remove");
@@ -1857,7 +1865,7 @@ function parse(text, baseLocation) {
1857
1865
  begin++;
1858
1866
  continue;
1859
1867
  }
1860
- const token = text.substring(begin, end);
1868
+ const token = text.slice(begin, end);
1861
1869
  tokens.push(token);
1862
1870
  if (locations && baseLocation) {
1863
1871
  const location = sliceLocation(baseLocation, begin, end);
@@ -1872,7 +1880,7 @@ class DOMTokenList extends Array {
1872
1880
  locations;
1873
1881
  constructor(value, location) {
1874
1882
  if (value && typeof value === "string") {
1875
- const normalized = value.replace(/[\t\r\n]/g, " ");
1883
+ const normalized = value.replaceAll(/[\t\r\n]/g, " ");
1876
1884
  const { tokens, locations } = parse(normalized, location);
1877
1885
  super(...tokens);
1878
1886
  this.locations = locations;
@@ -1962,7 +1970,7 @@ function nthChild(node, args) {
1962
1970
  if (!args) {
1963
1971
  throw new Error("Missing argument to nth-child");
1964
1972
  }
1965
- const n = parseInt(args.trim(), 10);
1973
+ const n = Number.parseInt(args.trim(), 10);
1966
1974
  const cur = getNthChild(node);
1967
1975
  return cur === n;
1968
1976
  }
@@ -1987,7 +1995,7 @@ function factory(name, context) {
1987
1995
  }
1988
1996
 
1989
1997
  function stripslashes(value) {
1990
- return value.replace(/\\(.)/g, "$1");
1998
+ return value.replaceAll(/\\(.)/g, "$1");
1991
1999
  }
1992
2000
  class Condition {
1993
2001
  }
@@ -2186,7 +2194,7 @@ function candidatesFromCombinator(element, combinator) {
2186
2194
  }
2187
2195
  }
2188
2196
  function matchElement(element, compounds, context) {
2189
- const last = compounds[compounds.length - 1];
2197
+ const last = compounds.at(-1);
2190
2198
  if (!last.match(element, context)) {
2191
2199
  return false;
2192
2200
  }
@@ -2203,7 +2211,7 @@ function matchElement(element, compounds, context) {
2203
2211
  return false;
2204
2212
  }
2205
2213
 
2206
- const escapedCodepoints = ["9", "a", "d"];
2214
+ const escapedCodepoints = /* @__PURE__ */ new Set(["9", "a", "d"]);
2207
2215
  function* splitSelectorElements(selector) {
2208
2216
  let begin = 0;
2209
2217
  let end = 0;
@@ -2218,7 +2226,7 @@ function* splitSelectorElements(selector) {
2218
2226
  return 0 /* INITIAL */;
2219
2227
  }
2220
2228
  function escapedState(ch) {
2221
- if (escapedCodepoints.includes(ch)) {
2229
+ if (escapedCodepoints.has(ch)) {
2222
2230
  return 1 /* ESCAPED */;
2223
2231
  }
2224
2232
  return 0 /* INITIAL */;
@@ -2258,7 +2266,7 @@ function unescapeCodepoint(value) {
2258
2266
  "\\a ": "\n",
2259
2267
  "\\d ": "\r"
2260
2268
  };
2261
- return value.replace(
2269
+ return value.replaceAll(
2262
2270
  /(\\[\u0039\u0061\u0064] )/g,
2263
2271
  (_, codepoint) => replacement[codepoint]
2264
2272
  );
@@ -2269,7 +2277,7 @@ function escapeSelectorComponent(text) {
2269
2277
  "\n": "\\a ",
2270
2278
  "\r": "\\d "
2271
2279
  };
2272
- return text.toString().replace(/([\t\n\r]|[^a-z0-9_-])/gi, (_, ch) => {
2280
+ return text.toString().replaceAll(/([\t\n\r]|[^a-z0-9_-])/gi, (_, ch) => {
2273
2281
  if (codepoints[ch]) {
2274
2282
  return codepoints[ch];
2275
2283
  } else {
@@ -2318,7 +2326,7 @@ class Selector {
2318
2326
  }
2319
2327
  }
2320
2328
  static parse(selector) {
2321
- selector = selector.replace(/([+~>]) /g, "$1");
2329
+ selector = selector.replaceAll(/([+~>]) /g, "$1");
2322
2330
  return Array.from(splitSelectorElements(selector), (element) => {
2323
2331
  return new Compound(unescapeCodepoint(element));
2324
2332
  });
@@ -2559,7 +2567,7 @@ class HtmlElement extends DOMNode {
2559
2567
  return attr.value;
2560
2568
  }
2561
2569
  const list = new DOMTokenList(attr.value, attr.valueLocation);
2562
- return list.length ? Array.from(list) : null;
2570
+ return list.length > 0 ? Array.from(list) : null;
2563
2571
  }
2564
2572
  /**
2565
2573
  * Similar to childNodes but only elements.
@@ -2614,7 +2622,7 @@ class HtmlElement extends DOMNode {
2614
2622
  }
2615
2623
  parts.push(`${cur.tagName.toLowerCase()}:nth-child(${String(index + 1)})`);
2616
2624
  }
2617
- return parts.reverse().join(" > ");
2625
+ return parts.toReversed().join(" > ");
2618
2626
  }
2619
2627
  /**
2620
2628
  * Tests if this element has given tagname.
@@ -2656,7 +2664,7 @@ class HtmlElement extends DOMNode {
2656
2664
  this.metaElement ??= {};
2657
2665
  for (const key of MetaCopyableProperty) {
2658
2666
  const value = meta[key];
2659
- if (typeof value !== "undefined") {
2667
+ if (value !== void 0) {
2660
2668
  setMetaProperty(this.metaElement, key, value);
2661
2669
  } else {
2662
2670
  delete this.metaElement[key];
@@ -2760,8 +2768,8 @@ class HtmlElement extends DOMNode {
2760
2768
  if (tabindex.value instanceof DynamicValue) {
2761
2769
  return this.cacheSet(TABINDEX, 0);
2762
2770
  }
2763
- const parsed = parseInt(tabindex.value, 10);
2764
- if (isNaN(parsed)) {
2771
+ const parsed = Number.parseInt(tabindex.value, 10);
2772
+ if (Number.isNaN(parsed)) {
2765
2773
  return this.cacheSet(TABINDEX, null);
2766
2774
  }
2767
2775
  return this.cacheSet(TABINDEX, parsed);
@@ -2865,7 +2873,10 @@ class HtmlElement extends DOMNode {
2865
2873
  */
2866
2874
  get lastElementChild() {
2867
2875
  const children = this.childElements;
2868
- return children.length > 0 ? children[children.length - 1] : null;
2876
+ return children.length > 0 ? (
2877
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it is checked right before this */
2878
+ children.at(-1)
2879
+ ) : null;
2869
2880
  }
2870
2881
  get siblings() {
2871
2882
  return this.parent ? this.parent.childElements : [this];
@@ -2988,7 +2999,9 @@ function depthFirst(root, callback) {
2988
2999
  root = root.root;
2989
3000
  }
2990
3001
  function visit(node) {
2991
- node.childElements.forEach(visit);
3002
+ for (const child of node.childElements) {
3003
+ visit(child);
3004
+ }
2992
3005
  if (!node.isRootElement()) {
2993
3006
  callback(node);
2994
3007
  }
@@ -3077,7 +3090,7 @@ class DOMTree {
3077
3090
  }
3078
3091
  }
3079
3092
 
3080
- const allowedKeys = ["exclude"];
3093
+ const allowedKeys = /* @__PURE__ */ new Set(["exclude"]);
3081
3094
  class Validator {
3082
3095
  /**
3083
3096
  * Test if element is used in a proper context.
@@ -3316,7 +3329,7 @@ class Validator {
3316
3329
  }
3317
3330
  function validateKeys(rule) {
3318
3331
  for (const key of Object.keys(rule)) {
3319
- if (!allowedKeys.includes(key)) {
3332
+ if (!allowedKeys.has(key)) {
3320
3333
  const str = JSON.stringify(rule);
3321
3334
  throw new Error(`Permitted rule "${str}" contains unknown property "${key}"`);
3322
3335
  }
@@ -3358,7 +3371,7 @@ function parseSeverity(value) {
3358
3371
 
3359
3372
  const cacheKey = /* @__PURE__ */ Symbol("aria-naming");
3360
3373
  const defaultValue = "allowed";
3361
- const prohibitedRoles = [
3374
+ const prohibitedRoles = /* @__PURE__ */ new Set([
3362
3375
  "caption",
3363
3376
  "code",
3364
3377
  "deletion",
@@ -3370,9 +3383,9 @@ const prohibitedRoles = [
3370
3383
  "strong",
3371
3384
  "subscript",
3372
3385
  "superscript"
3373
- ];
3386
+ ]);
3374
3387
  function byRole(role) {
3375
- return prohibitedRoles.includes(role) ? "prohibited" : "allowed";
3388
+ return prohibitedRoles.has(role) ? "prohibited" : "allowed";
3376
3389
  }
3377
3390
  function byMeta(element, meta) {
3378
3391
  return meta.aria.naming(element._adapter);
@@ -3424,7 +3437,7 @@ function isInputDisabledImpl(node) {
3424
3437
 
3425
3438
  const patternCache = /* @__PURE__ */ new Map();
3426
3439
  function compileStringPattern(pattern) {
3427
- const regexp = pattern.replace(/[*]+/g, ".+");
3440
+ const regexp = pattern.replaceAll(/[*]+/g, ".+");
3428
3441
  return new RegExp(`^${regexp}$`);
3429
3442
  }
3430
3443
  function compileRegExpPattern(pattern) {
@@ -3699,8 +3712,8 @@ function format(value, quote = false) {
3699
3712
  return String(value);
3700
3713
  }
3701
3714
  function interpolate(text, data) {
3702
- return text.replace(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
3703
- return typeof data[key] !== "undefined" ? format(data[key]) : match;
3715
+ return text.replaceAll(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
3716
+ return data[key] !== void 0 ? format(data[key]) : match;
3704
3717
  });
3705
3718
  }
3706
3719
 
@@ -3908,7 +3921,14 @@ class Rule {
3908
3921
  });
3909
3922
  if (enabled && !blocked) {
3910
3923
  const interpolated = interpolate(message, context ?? {});
3911
- this.reporter.add(this, interpolated, this.severity, node, where, context);
3924
+ this.reporter.add({
3925
+ rule: this,
3926
+ message: interpolated,
3927
+ severity: this.severity,
3928
+ node,
3929
+ location: where,
3930
+ context
3931
+ });
3912
3932
  }
3913
3933
  }
3914
3934
  findLocation(src) {
@@ -3999,7 +4019,7 @@ class Rule {
3999
4019
  }
4000
4020
  }
4001
4021
 
4002
- const defaults$B = {
4022
+ const defaults$C = {
4003
4023
  allowExternal: true,
4004
4024
  allowRelative: true,
4005
4025
  allowAbsolute: true,
@@ -4043,7 +4063,7 @@ class AllowedLinks extends Rule {
4043
4063
  allowRelative;
4044
4064
  allowAbsolute;
4045
4065
  constructor(options) {
4046
- super({ ...defaults$B, ...options });
4066
+ super({ ...defaults$C, ...options });
4047
4067
  this.allowExternal = parseAllow(this.options.allowExternal);
4048
4068
  this.allowRelative = parseAllow(this.options.allowRelative);
4049
4069
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4211,7 +4231,7 @@ class AllowedLinks extends Rule {
4211
4231
  }
4212
4232
  }
4213
4233
 
4214
- const defaults$A = {
4234
+ const defaults$B = {
4215
4235
  accessible: true
4216
4236
  };
4217
4237
  function findByTarget(target, siblings) {
@@ -4241,7 +4261,7 @@ function getDescription$1(context) {
4241
4261
  }
4242
4262
  class AreaAlt extends Rule {
4243
4263
  constructor(options) {
4244
- super({ ...defaults$A, ...options });
4264
+ super({ ...defaults$B, ...options });
4245
4265
  }
4246
4266
  static schema() {
4247
4267
  return {
@@ -4320,7 +4340,7 @@ class AriaHiddenBody extends Rule {
4320
4340
  }
4321
4341
  }
4322
4342
 
4323
- const defaults$z = {
4343
+ const defaults$A = {
4324
4344
  allowAnyNamable: false,
4325
4345
  elements: {
4326
4346
  include: null,
@@ -4368,7 +4388,7 @@ function isValidUsage(target, meta) {
4368
4388
  }
4369
4389
  class AriaLabelMisuse extends Rule {
4370
4390
  constructor(options) {
4371
- super({ ...defaults$z, ...options });
4391
+ super({ ...defaults$A, ...options });
4372
4392
  }
4373
4393
  static schema() {
4374
4394
  return {
@@ -4480,8 +4500,7 @@ class AriaLabelMisuse extends Rule {
4480
4500
  class ConfigError extends UserError {
4481
4501
  constructor(message, nested) {
4482
4502
  super(message, nested);
4483
- Error.captureStackTrace(this, ConfigError);
4484
- this.name = ConfigError.name;
4503
+ this.name = "ConfigError";
4485
4504
  }
4486
4505
  }
4487
4506
 
@@ -4537,14 +4556,14 @@ class CaseStyle {
4537
4556
  }
4538
4557
  }
4539
4558
 
4540
- const defaults$y = {
4559
+ const defaults$z = {
4541
4560
  style: "lowercase",
4542
4561
  ignoreForeign: true
4543
4562
  };
4544
4563
  class AttrCase extends Rule {
4545
4564
  style;
4546
4565
  constructor(options) {
4547
- super({ ...defaults$y, ...options });
4566
+ super({ ...defaults$z, ...options });
4548
4567
  this.style = new CaseStyle(this.options.style, "attr-case");
4549
4568
  }
4550
4569
  static schema() {
@@ -4585,7 +4604,7 @@ class AttrCase extends Rule {
4585
4604
  if (event.originalAttribute) {
4586
4605
  return;
4587
4606
  }
4588
- const letters = event.key.replace(/[^a-z]+/gi, "");
4607
+ const letters = event.key.replaceAll(/[^a-z]+/gi, "");
4589
4608
  if (this.style.match(letters)) {
4590
4609
  return;
4591
4610
  }
@@ -4658,6 +4677,7 @@ class InvalidTokenError extends Error {
4658
4677
  location;
4659
4678
  constructor(location, message) {
4660
4679
  super(message);
4680
+ this.name = "InvalidTokenError";
4661
4681
  this.location = location;
4662
4682
  }
4663
4683
  }
@@ -4767,16 +4787,26 @@ class Lexer {
4767
4787
  */
4768
4788
  enter(context, state, data) {
4769
4789
  if (state === State.TAG && data?.[0].startsWith("<")) {
4770
- if (data[0] === "<script") {
4771
- context.contentModel = ContentModel.SCRIPT;
4772
- } else if (data[0] === "<style") {
4773
- context.contentModel = ContentModel.STYLE;
4774
- } else if (data[0] === "<textarea") {
4775
- context.contentModel = ContentModel.TEXTAREA;
4776
- } else if (data[0] === "<title") {
4777
- context.contentModel = ContentModel.TITLE;
4778
- } else {
4779
- context.contentModel = ContentModel.TEXT;
4790
+ switch (data[0]) {
4791
+ case "<script": {
4792
+ context.contentModel = ContentModel.SCRIPT;
4793
+ break;
4794
+ }
4795
+ case "<style": {
4796
+ context.contentModel = ContentModel.STYLE;
4797
+ break;
4798
+ }
4799
+ case "<textarea": {
4800
+ context.contentModel = ContentModel.TEXTAREA;
4801
+ break;
4802
+ }
4803
+ case "<title": {
4804
+ context.contentModel = ContentModel.TITLE;
4805
+ break;
4806
+ }
4807
+ default: {
4808
+ context.contentModel = ContentModel.TEXT;
4809
+ }
4780
4810
  }
4781
4811
  }
4782
4812
  }
@@ -4949,7 +4979,7 @@ class AttrDelimiter extends Rule {
4949
4979
  }
4950
4980
 
4951
4981
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4952
- const defaults$x = {
4982
+ const defaults$y = {
4953
4983
  pattern: DEFAULT_PATTERN,
4954
4984
  ignoreForeign: true
4955
4985
  };
@@ -4982,7 +5012,7 @@ function generateDescription(name, pattern) {
4982
5012
  class AttrPattern extends Rule {
4983
5013
  pattern;
4984
5014
  constructor(options) {
4985
- super({ ...defaults$x, ...options });
5015
+ super({ ...defaults$y, ...options });
4986
5016
  this.pattern = generateRegexp(this.options.pattern);
4987
5017
  }
4988
5018
  static schema() {
@@ -5029,7 +5059,7 @@ class AttrPattern extends Rule {
5029
5059
  }
5030
5060
  }
5031
5061
 
5032
- const defaults$w = {
5062
+ const defaults$x = {
5033
5063
  style: "auto",
5034
5064
  unquoted: false
5035
5065
  };
@@ -5095,7 +5125,7 @@ class AttrQuotes extends Rule {
5095
5125
  };
5096
5126
  }
5097
5127
  constructor(options) {
5098
- super({ ...defaults$w, ...options });
5128
+ super({ ...defaults$x, ...options });
5099
5129
  this.style = parseStyle$3(this.options.style);
5100
5130
  }
5101
5131
  setup() {
@@ -5177,10 +5207,10 @@ class AttrSpacing extends Rule {
5177
5207
 
5178
5208
  function pick(attr) {
5179
5209
  const result = {};
5180
- if (typeof attr.enum !== "undefined") {
5210
+ if (attr.enum !== void 0) {
5181
5211
  result.enum = attr.enum;
5182
5212
  }
5183
- if (typeof attr.boolean !== "undefined") {
5213
+ if (attr.boolean !== void 0) {
5184
5214
  result.boolean = attr.boolean;
5185
5215
  }
5186
5216
  return result;
@@ -5251,13 +5281,13 @@ class AttributeAllowedValues extends Rule {
5251
5281
  }
5252
5282
  }
5253
5283
 
5254
- const defaults$v = {
5284
+ const defaults$w = {
5255
5285
  style: "omit"
5256
5286
  };
5257
5287
  class AttributeBooleanStyle extends Rule {
5258
5288
  hasInvalidStyle;
5259
5289
  constructor(options) {
5260
- super({ ...defaults$v, ...options });
5290
+ super({ ...defaults$w, ...options });
5261
5291
  this.hasInvalidStyle = parseStyle$2(this.options.style);
5262
5292
  }
5263
5293
  static schema() {
@@ -5327,13 +5357,13 @@ function reportMessage$1(attr, style) {
5327
5357
  return "";
5328
5358
  }
5329
5359
 
5330
- const defaults$u = {
5360
+ const defaults$v = {
5331
5361
  style: "omit"
5332
5362
  };
5333
5363
  class AttributeEmptyStyle extends Rule {
5334
5364
  hasInvalidStyle;
5335
5365
  constructor(options) {
5336
- super({ ...defaults$u, ...options });
5366
+ super({ ...defaults$v, ...options });
5337
5367
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5338
5368
  }
5339
5369
  static schema() {
@@ -5450,7 +5480,7 @@ class AttributeMisuse extends Rule {
5450
5480
  }
5451
5481
  }
5452
5482
 
5453
- const defaults$t = {
5483
+ const defaults$u = {
5454
5484
  preferred: void 0
5455
5485
  };
5456
5486
  function isPasswordInput(event) {
@@ -5467,7 +5497,7 @@ function isGroupingToken(token) {
5467
5497
  class AutocompletePassword extends Rule {
5468
5498
  preferred;
5469
5499
  constructor(options) {
5470
- super({ ...defaults$t, ...options });
5500
+ super({ ...defaults$u, ...options });
5471
5501
  this.preferred = options.preferred?.toLowerCase();
5472
5502
  }
5473
5503
  static schema() {
@@ -5546,21 +5576,19 @@ class AutocompletePassword extends Rule {
5546
5576
  });
5547
5577
  return;
5548
5578
  }
5549
- if (preferred) {
5550
- if (value !== preferred) {
5551
- const context = {
5552
- kind: "preferred-mismatch",
5553
- value,
5554
- preferred
5555
- };
5556
- this.report({
5557
- node: target,
5558
- message: `<input type="password"> should use autocomplete="${preferred}"`,
5559
- /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- location must be present if value is */
5560
- location,
5561
- context
5562
- });
5563
- }
5579
+ if (preferred && value !== preferred) {
5580
+ const context = {
5581
+ kind: "preferred-mismatch",
5582
+ value,
5583
+ preferred
5584
+ };
5585
+ this.report({
5586
+ node: target,
5587
+ message: `<input type="password"> should use autocomplete="${preferred}"`,
5588
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- location must be present if value is */
5589
+ location,
5590
+ context
5591
+ });
5564
5592
  }
5565
5593
  });
5566
5594
  }
@@ -5697,7 +5725,7 @@ class BasePatternRule extends Rule {
5697
5725
  }
5698
5726
  }
5699
5727
 
5700
- const defaults$s = {
5728
+ const defaults$t = {
5701
5729
  pattern: "kebabcase"
5702
5730
  };
5703
5731
  class ClassPattern extends BasePatternRule {
@@ -5705,7 +5733,7 @@ class ClassPattern extends BasePatternRule {
5705
5733
  super({
5706
5734
  ruleId: "class-pattern",
5707
5735
  attr: "class",
5708
- options: { ...defaults$s, ...options },
5736
+ options: { ...defaults$t, ...options },
5709
5737
  allowedPatterns: patternNames
5710
5738
  // allow all patterns
5711
5739
  });
@@ -5855,13 +5883,13 @@ class CloseOrder extends Rule {
5855
5883
  }
5856
5884
  }
5857
5885
 
5858
- const defaults$r = {
5886
+ const defaults$s = {
5859
5887
  include: null,
5860
5888
  exclude: null
5861
5889
  };
5862
5890
  class Deprecated extends Rule {
5863
5891
  constructor(options) {
5864
- super({ ...defaults$r, ...options });
5892
+ super({ ...defaults$s, ...options });
5865
5893
  }
5866
5894
  static schema() {
5867
5895
  return {
@@ -5907,7 +5935,7 @@ class Deprecated extends Rule {
5907
5935
  text.push(context.documentation);
5908
5936
  }
5909
5937
  const doc = {
5910
- description: text.map((cur) => cur.replace(/\$tagname/g, context.tagName)).join("\n\n"),
5938
+ description: text.map((cur) => cur.replaceAll("$tagname", context.tagName)).join("\n\n"),
5911
5939
  url: "https://html-validate.org/rules/deprecated.html"
5912
5940
  };
5913
5941
  return doc;
@@ -5973,7 +6001,7 @@ function quote(value, char = '"') {
5973
6001
  return `${char}${value}${char}`;
5974
6002
  }
5975
6003
 
5976
- const defaults$q = {
6004
+ const defaults$r = {
5977
6005
  classes: []
5978
6006
  };
5979
6007
  function isRelevant$6(event) {
@@ -6027,7 +6055,7 @@ ${listItems.join("\n")}`);
6027
6055
  class DeprecatedClass extends Rule {
6028
6056
  deprecatedMap;
6029
6057
  constructor(options) {
6030
- super({ ...defaults$q, ...options });
6058
+ super({ ...defaults$r, ...options });
6031
6059
  const { classes } = this.options;
6032
6060
  this.deprecatedMap = new Map(classes.map((entry) => [entry.class, normalizeEntry(entry)]));
6033
6061
  }
@@ -6146,12 +6174,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
6146
6174
  }
6147
6175
  };
6148
6176
 
6149
- const defaults$p = {
6177
+ const defaults$q = {
6150
6178
  style: "uppercase"
6151
6179
  };
6152
6180
  class DoctypeStyle extends Rule {
6153
6181
  constructor(options) {
6154
- super({ ...defaults$p, ...options });
6182
+ super({ ...defaults$q, ...options });
6155
6183
  }
6156
6184
  static schema() {
6157
6185
  return {
@@ -6179,13 +6207,13 @@ class DoctypeStyle extends Rule {
6179
6207
  }
6180
6208
  }
6181
6209
 
6182
- const defaults$o = {
6210
+ const defaults$p = {
6183
6211
  style: "lowercase"
6184
6212
  };
6185
6213
  class ElementCase extends Rule {
6186
6214
  style;
6187
6215
  constructor(options) {
6188
- super({ ...defaults$o, ...options });
6216
+ super({ ...defaults$p, ...options });
6189
6217
  this.style = new CaseStyle(this.options.style, "element-case");
6190
6218
  }
6191
6219
  static schema() {
@@ -6226,7 +6254,7 @@ class ElementCase extends Rule {
6226
6254
  });
6227
6255
  }
6228
6256
  validateCase(target, targetLocation) {
6229
- const letters = target.tagName.replace(/[^a-z]+/gi, "");
6257
+ const letters = target.tagName.replaceAll(/[^a-z]+/gi, "");
6230
6258
  if (!this.style.match(letters)) {
6231
6259
  const location = sliceLocation(targetLocation, 1);
6232
6260
  this.report(target, `Element "${target.tagName}" should be ${this.style.name}`, location);
@@ -6245,7 +6273,7 @@ class ElementCase extends Rule {
6245
6273
  }
6246
6274
  }
6247
6275
 
6248
- const defaults$n = {
6276
+ const defaults$o = {
6249
6277
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
6250
6278
  whitelist: [],
6251
6279
  blacklist: []
@@ -6253,7 +6281,7 @@ const defaults$n = {
6253
6281
  class ElementName extends Rule {
6254
6282
  pattern;
6255
6283
  constructor(options) {
6256
- super({ ...defaults$n, ...options });
6284
+ super({ ...defaults$o, ...options });
6257
6285
  this.pattern = new RegExp(this.options.pattern);
6258
6286
  }
6259
6287
  static schema() {
@@ -6290,7 +6318,7 @@ class ElementName extends Rule {
6290
6318
  ...context.blacklist.map((cur) => `- ${cur}`)
6291
6319
  ];
6292
6320
  }
6293
- if (context.pattern !== defaults$n.pattern) {
6321
+ if (context.pattern !== defaults$o.pattern) {
6294
6322
  return [
6295
6323
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
6296
6324
  "",
@@ -6807,7 +6835,7 @@ class EmptyTitle extends Rule {
6807
6835
  }
6808
6836
  }
6809
6837
 
6810
- const defaults$m = {
6838
+ const defaults$n = {
6811
6839
  allowArrayBrackets: true,
6812
6840
  allowCheckboxDefault: true,
6813
6841
  shared: ["radio", "button", "reset", "submit"]
@@ -6830,8 +6858,8 @@ function haveName(name) {
6830
6858
  return typeof name === "string" && name !== "";
6831
6859
  }
6832
6860
  function allowSharedName(node, shared) {
6833
- const type = node.getAttribute("type");
6834
- return Boolean(type?.valueMatches(shared, false));
6861
+ const type = getControlType(node);
6862
+ return shared.includes(type);
6835
6863
  }
6836
6864
  function isInputHidden(element) {
6837
6865
  return element.is("input") && element.getAttributeValue("type") === "hidden";
@@ -6839,6 +6867,13 @@ function isInputHidden(element) {
6839
6867
  function isInputCheckbox(element) {
6840
6868
  return element.is("input") && element.getAttributeValue("type") === "checkbox";
6841
6869
  }
6870
+ function getControlType(element) {
6871
+ const type = element.getAttributeValue("type") ?? "";
6872
+ if (element.is("button") && type === "") {
6873
+ return "submit";
6874
+ }
6875
+ return type;
6876
+ }
6842
6877
  function isCheckboxWithDefault(control, previous, options) {
6843
6878
  const { allowCheckboxDefault } = options;
6844
6879
  if (!allowCheckboxDefault) {
@@ -6867,7 +6902,7 @@ function getDocumentation(context) {
6867
6902
  }
6868
6903
  class FormDupName extends Rule {
6869
6904
  constructor(options) {
6870
- super({ ...defaults$m, ...options });
6905
+ super({ ...defaults$n, ...options });
6871
6906
  }
6872
6907
  static schema() {
6873
6908
  return {
@@ -6976,7 +7011,7 @@ class FormDupName extends Rule {
6976
7011
  validateSharedName(control, group, attr, name) {
6977
7012
  const uniqueElements = this.getUniqueElements(group);
6978
7013
  const sharedElements = this.getSharedElements(group);
6979
- const type = control.getAttributeValue("type") ?? "";
7014
+ const type = getControlType(control);
6980
7015
  if (uniqueElements.has(name) || sharedElements.has(name) && sharedElements.get(name) !== type) {
6981
7016
  const context = {
6982
7017
  name,
@@ -7026,7 +7061,7 @@ class FormDupName extends Rule {
7026
7061
  }
7027
7062
  }
7028
7063
 
7029
- const defaults$l = {
7064
+ const defaults$m = {
7030
7065
  allowMultipleH1: false,
7031
7066
  minInitialRank: "h1",
7032
7067
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
@@ -7038,7 +7073,7 @@ function isRelevant$5(event) {
7038
7073
  function extractLevel(node) {
7039
7074
  const match = /^[hH](\d)$/.exec(node.tagName);
7040
7075
  if (match) {
7041
- return parseInt(match[1], 10);
7076
+ return Number.parseInt(match[1], 10);
7042
7077
  } else {
7043
7078
  return null;
7044
7079
  }
@@ -7051,14 +7086,14 @@ function parseMaxInitial(value) {
7051
7086
  if (!match) {
7052
7087
  return 1;
7053
7088
  }
7054
- return parseInt(match[1], 10);
7089
+ return Number.parseInt(match[1], 10);
7055
7090
  }
7056
7091
  class HeadingLevel extends Rule {
7057
7092
  minInitialRank;
7058
7093
  sectionRoots;
7059
7094
  stack = [];
7060
7095
  constructor(options) {
7061
- super({ ...defaults$l, ...options });
7096
+ super({ ...defaults$m, ...options });
7062
7097
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
7063
7098
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Compound(it));
7064
7099
  this.stack.push({
@@ -7200,10 +7235,10 @@ class HeadingLevel extends Rule {
7200
7235
  this.stack.pop();
7201
7236
  }
7202
7237
  getPrevRoot() {
7203
- return this.stack[this.stack.length - 2];
7238
+ return this.stack.at(-2);
7204
7239
  }
7205
7240
  getCurrentRoot() {
7206
- return this.stack[this.stack.length - 1];
7241
+ return this.stack.at(-1);
7207
7242
  }
7208
7243
  isSectioningRoot(node) {
7209
7244
  const context = {
@@ -7297,7 +7332,7 @@ class HiddenFocusable extends Rule {
7297
7332
  }
7298
7333
  }
7299
7334
 
7300
- const defaults$k = {
7335
+ const defaults$l = {
7301
7336
  pattern: "kebabcase"
7302
7337
  };
7303
7338
  function exclude$1(set, ...values) {
@@ -7313,7 +7348,7 @@ class IdPattern extends BasePatternRule {
7313
7348
  super({
7314
7349
  ruleId: "id-pattern",
7315
7350
  attr: "id",
7316
- options: { ...defaults$k, ...options },
7351
+ options: { ...defaults$l, ...options },
7317
7352
  allowedPatterns
7318
7353
  });
7319
7354
  }
@@ -7635,13 +7670,13 @@ function findLabelByParent(el) {
7635
7670
  return [];
7636
7671
  }
7637
7672
 
7638
- const defaults$j = {
7673
+ const defaults$k = {
7639
7674
  maxlength: 70
7640
7675
  };
7641
7676
  class LongTitle extends Rule {
7642
7677
  maxlength;
7643
7678
  constructor(options) {
7644
- super({ ...defaults$j, ...options });
7679
+ super({ ...defaults$k, ...options });
7645
7680
  this.maxlength = this.options.maxlength;
7646
7681
  }
7647
7682
  static schema() {
@@ -7745,12 +7780,12 @@ class MapIdName extends Rule {
7745
7780
  }
7746
7781
  }
7747
7782
 
7748
- const defaults$i = {
7783
+ const defaults$j = {
7749
7784
  allowLongDelay: false
7750
7785
  };
7751
7786
  class MetaRefresh extends Rule {
7752
7787
  constructor(options) {
7753
- super({ ...defaults$i, ...options });
7788
+ super({ ...defaults$j, ...options });
7754
7789
  }
7755
7790
  documentation() {
7756
7791
  return {
@@ -7800,7 +7835,7 @@ function parseContent(text) {
7800
7835
  const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7801
7836
  if (match) {
7802
7837
  return {
7803
- delay: parseInt(match[1], 10),
7838
+ delay: Number.parseInt(match[1], 10),
7804
7839
  url: match[2]
7805
7840
  };
7806
7841
  } else {
@@ -7861,7 +7896,7 @@ class MultipleLabeledControls extends Rule {
7861
7896
  }
7862
7897
  }
7863
7898
 
7864
- const defaults$h = {
7899
+ const defaults$i = {
7865
7900
  pattern: "camelcase"
7866
7901
  };
7867
7902
  function exclude(set, ...values) {
@@ -7877,7 +7912,7 @@ class NamePattern extends BasePatternRule {
7877
7912
  super({
7878
7913
  ruleId: "name-pattern",
7879
7914
  attr: "name",
7880
- options: { ...defaults$h, ...options },
7915
+ options: { ...defaults$i, ...options },
7881
7916
  allowedPatterns
7882
7917
  });
7883
7918
  }
@@ -7968,13 +8003,13 @@ class NoAbstractRole extends Rule {
7968
8003
  }
7969
8004
  }
7970
8005
 
7971
- const defaults$g = {
8006
+ const defaults$h = {
7972
8007
  include: null,
7973
8008
  exclude: null
7974
8009
  };
7975
8010
  class NoAutoplay extends Rule {
7976
8011
  constructor(options) {
7977
- super({ ...defaults$g, ...options });
8012
+ super({ ...defaults$h, ...options });
7978
8013
  }
7979
8014
  documentation(context) {
7980
8015
  return {
@@ -8124,13 +8159,12 @@ class NoDupClass extends Rule {
8124
8159
  }
8125
8160
  const classes = new DOMTokenList(event.value, event.valueLocation);
8126
8161
  const unique = /* @__PURE__ */ new Set();
8127
- classes.forEach((cur, index) => {
8128
- if (unique.has(cur)) {
8129
- const location = classes.location(index);
8130
- this.report(event.target, `Class "${cur}" duplicated`, location);
8162
+ for (const { item, location } of classes.iterator()) {
8163
+ if (unique.has(item)) {
8164
+ this.report(event.target, `Class "${item}" duplicated`, location);
8131
8165
  }
8132
- unique.add(cur);
8133
- });
8166
+ unique.add(item);
8167
+ }
8134
8168
  });
8135
8169
  }
8136
8170
  }
@@ -8296,14 +8330,14 @@ class NoImplicitInputType extends Rule {
8296
8330
  }
8297
8331
  }
8298
8332
 
8299
- const defaults$f = {
8333
+ const defaults$g = {
8300
8334
  include: null,
8301
8335
  exclude: null,
8302
8336
  allowedProperties: ["display"]
8303
8337
  };
8304
8338
  class NoInlineStyle extends Rule {
8305
8339
  constructor(options) {
8306
- super({ ...defaults$f, ...options });
8340
+ super({ ...defaults$g, ...options });
8307
8341
  }
8308
8342
  static schema() {
8309
8343
  return {
@@ -8489,7 +8523,7 @@ class NoMultipleMain extends Rule {
8489
8523
  }
8490
8524
  }
8491
8525
 
8492
- const defaults$e = {
8526
+ const defaults$f = {
8493
8527
  relaxed: false
8494
8528
  };
8495
8529
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -8507,7 +8541,7 @@ const replacementTable = {
8507
8541
  class NoRawCharacters extends Rule {
8508
8542
  relaxed;
8509
8543
  constructor(options) {
8510
- super({ ...defaults$e, ...options });
8544
+ super({ ...defaults$f, ...options });
8511
8545
  this.relaxed = this.options.relaxed;
8512
8546
  }
8513
8547
  static schema() {
@@ -8646,7 +8680,14 @@ class NoRedundantFor extends Rule {
8646
8680
  }
8647
8681
  }
8648
8682
 
8683
+ const defaults$e = {
8684
+ include: null,
8685
+ exclude: null
8686
+ };
8649
8687
  class NoRedundantRole extends Rule {
8688
+ constructor(options) {
8689
+ super({ ...defaults$e, ...options });
8690
+ }
8650
8691
  documentation(context) {
8651
8692
  const { role, tagName } = context;
8652
8693
  return {
@@ -8654,6 +8695,36 @@ class NoRedundantRole extends Rule {
8654
8695
  url: "https://html-validate.org/rules/no-redundant-role.html"
8655
8696
  };
8656
8697
  }
8698
+ static schema() {
8699
+ return {
8700
+ exclude: {
8701
+ anyOf: [
8702
+ {
8703
+ items: {
8704
+ type: "string"
8705
+ },
8706
+ type: "array"
8707
+ },
8708
+ {
8709
+ type: "null"
8710
+ }
8711
+ ]
8712
+ },
8713
+ include: {
8714
+ anyOf: [
8715
+ {
8716
+ items: {
8717
+ type: "string"
8718
+ },
8719
+ type: "array"
8720
+ },
8721
+ {
8722
+ type: "null"
8723
+ }
8724
+ ]
8725
+ }
8726
+ };
8727
+ }
8657
8728
  setup() {
8658
8729
  this.on("tag:ready", (event) => {
8659
8730
  const { target } = event;
@@ -8672,6 +8743,9 @@ class NoRedundantRole extends Rule {
8672
8743
  if (role.value !== implicitRole) {
8673
8744
  return;
8674
8745
  }
8746
+ if (this.isKeywordIgnored(role.value)) {
8747
+ return;
8748
+ }
8675
8749
  const context = {
8676
8750
  tagName: target.tagName,
8677
8751
  role: role.value
@@ -8862,10 +8936,10 @@ class NoUnusedDisable extends Rule {
8862
8936
  setup() {
8863
8937
  }
8864
8938
  reportUnused(unused, options, location) {
8865
- const tokens = new DOMTokenList(options.replace(/,/g, " "), location);
8939
+ const tokens = new DOMTokenList(options.replaceAll(",", " "), location);
8866
8940
  for (const ruleId of unused) {
8867
8941
  const index = tokens.indexOf(ruleId);
8868
- const tokenLocation = index >= 0 ? tokens.location(index) : location;
8942
+ const tokenLocation = index !== -1 ? tokens.location(index) : location;
8869
8943
  this.report({
8870
8944
  node: null,
8871
8945
  message: '"{{ ruleId }}" rule is disabled but no error was reported',
@@ -9113,7 +9187,7 @@ class PreferTbody extends Rule {
9113
9187
  continue;
9114
9188
  }
9115
9189
  const tr = table.querySelectorAll("> tr");
9116
- if (tr.length >= 1) {
9190
+ if (tr.length > 0) {
9117
9191
  this.report(tr[0], "Prefer to wrap <tr> elements in <tbody>");
9118
9192
  }
9119
9193
  }
@@ -9183,19 +9257,19 @@ const supportSri = {
9183
9257
  link: "href",
9184
9258
  script: "src"
9185
9259
  };
9186
- const supportedRel = ["stylesheet", "preload", "modulepreload"];
9187
- const supportedPreload = ["style", "script"];
9260
+ const supportedRel = /* @__PURE__ */ new Set(["stylesheet", "preload", "modulepreload"]);
9261
+ const supportedPreload = /* @__PURE__ */ new Set(["style", "script"]);
9188
9262
  function linkSupportsSri(node) {
9189
9263
  const rel = node.getAttribute("rel");
9190
9264
  if (typeof rel?.value !== "string") {
9191
9265
  return false;
9192
9266
  }
9193
- if (!supportedRel.includes(rel.value)) {
9267
+ if (!supportedRel.has(rel.value)) {
9194
9268
  return false;
9195
9269
  }
9196
9270
  if (rel.value === "preload") {
9197
9271
  const as = node.getAttribute("as");
9198
- return typeof as?.value === "string" && supportedPreload.includes(as.value);
9272
+ return typeof as?.value === "string" && supportedPreload.has(as.value);
9199
9273
  }
9200
9274
  return true;
9201
9275
  }
@@ -9312,13 +9386,13 @@ class ScriptElement extends Rule {
9312
9386
  }
9313
9387
  }
9314
9388
 
9315
- const javascript = [
9389
+ const javascript = /* @__PURE__ */ new Set([
9316
9390
  "",
9317
9391
  "application/ecmascript",
9318
9392
  "application/javascript",
9319
9393
  "text/ecmascript",
9320
9394
  "text/javascript"
9321
- ];
9395
+ ]);
9322
9396
  class ScriptType extends Rule {
9323
9397
  documentation() {
9324
9398
  return {
@@ -9349,7 +9423,7 @@ class ScriptType extends Rule {
9349
9423
  }
9350
9424
  isJavascript(mime) {
9351
9425
  const type = mime.replace(/;.*/, "");
9352
- return javascript.includes(type);
9426
+ return javascript.has(type);
9353
9427
  }
9354
9428
  }
9355
9429
 
@@ -9925,7 +9999,7 @@ class UnknownCharReference extends Rule {
9925
9999
  }
9926
10000
 
9927
10001
  const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
9928
- const fieldNames1 = [
10002
+ const fieldNames1 = /* @__PURE__ */ new Set([
9929
10003
  "name",
9930
10004
  "honorific-prefix",
9931
10005
  "given-name",
@@ -9970,8 +10044,8 @@ const fieldNames1 = [
9970
10044
  "sex",
9971
10045
  "url",
9972
10046
  "photo"
9973
- ];
9974
- const fieldNames2 = [
10047
+ ]);
10048
+ const fieldNames2 = /* @__PURE__ */ new Set([
9975
10049
  "tel",
9976
10050
  "tel-country-code",
9977
10051
  "tel-national",
@@ -9982,7 +10056,7 @@ const fieldNames2 = [
9982
10056
  "tel-extension",
9983
10057
  "email",
9984
10058
  "impp"
9985
- ];
10059
+ ]);
9986
10060
  const fieldNameGroup = {
9987
10061
  name: "text",
9988
10062
  "honorific-prefix": "text",
@@ -10047,14 +10121,14 @@ function matchHint(token) {
10047
10121
  return token === "shipping" || token === "billing";
10048
10122
  }
10049
10123
  function matchFieldNames1(token) {
10050
- return fieldNames1.includes(token);
10124
+ return fieldNames1.has(token);
10051
10125
  }
10052
10126
  function matchContact(token) {
10053
10127
  const haystack = ["home", "work", "mobile", "fax", "pager"];
10054
10128
  return haystack.includes(token);
10055
10129
  }
10056
10130
  function matchFieldNames2(token) {
10057
- return fieldNames2.includes(token);
10131
+ return fieldNames2.has(token);
10058
10132
  }
10059
10133
  function matchWebauthn(token) {
10060
10134
  return token === "webauthn";
@@ -10725,11 +10799,13 @@ class H32 extends Rule {
10725
10799
  setup() {
10726
10800
  const formTags = this.getTagsWithProperty("form");
10727
10801
  const formSelector = formTags.join(",");
10802
+ const submitButtonTags = this.getTagsWithProperty("submitButton");
10803
+ const submitButtonSelector = submitButtonTags.join(",");
10728
10804
  this.on("dom:ready", (event) => {
10729
10805
  const { document } = event;
10730
10806
  const forms = document.querySelectorAll(formSelector);
10731
10807
  for (const form of forms) {
10732
- if (hasNestedSubmit(form)) {
10808
+ if (hasNestedSubmit(form, submitButtonSelector)) {
10733
10809
  continue;
10734
10810
  }
10735
10811
  if (hasAssociatedSubmit(document, form)) {
@@ -10741,15 +10817,15 @@ class H32 extends Rule {
10741
10817
  }
10742
10818
  }
10743
10819
  function isSubmit(node) {
10744
- const type = node.getAttribute("type");
10745
- return !type || type.valueMatches(/submit|image/);
10820
+ const meta = node.meta;
10821
+ return Boolean(meta?.submitButton);
10746
10822
  }
10747
10823
  function isAssociated(id, node) {
10748
10824
  const form = node.getAttribute("form");
10749
10825
  return Boolean(form?.valueMatches(id, true));
10750
10826
  }
10751
- function hasNestedSubmit(form) {
10752
- const matches = form.querySelectorAll("button,input").filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10827
+ function hasNestedSubmit(form, submitButtonSelector) {
10828
+ const matches = form.querySelectorAll(submitButtonSelector).filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10753
10829
  return matches.length > 0;
10754
10830
  }
10755
10831
  function hasAssociatedSubmit(document, form) {
@@ -10895,7 +10971,7 @@ function isSimpleTable(table) {
10895
10971
  }
10896
10972
  const shape = getShape(cells);
10897
10973
  const headersPerRow = cells.map((row) => row.reduce((sum, cell) => sum + cell, 0));
10898
- const headersPerColumn = Array(shape.cols).fill(0).map((_, index) => {
10974
+ const headersPerColumn = new Array(shape.cols).fill(0).map((_, index) => {
10899
10975
  return cells.reduce((sum, it) => sum + it[index], 0);
10900
10976
  });
10901
10977
  const [firstRow, ...otherRows] = headersPerRow;
@@ -11298,11 +11374,11 @@ function dumpTree(root) {
11298
11374
  } else {
11299
11375
  lines.push("(root)");
11300
11376
  }
11301
- node.childElements.forEach((child, index) => {
11377
+ for (const [index, child] of node.childElements.entries()) {
11302
11378
  const s = lastSibling ? " " : "\u2502";
11303
11379
  const i = level > 0 ? `${indent}${s} ` : "";
11304
11380
  writeNode(child, level + 1, i, index);
11305
- });
11381
+ }
11306
11382
  }
11307
11383
  writeNode(root, 0, "", 0);
11308
11384
  return lines;
@@ -12417,7 +12493,7 @@ class EventHandler {
12417
12493
  }
12418
12494
 
12419
12495
  const name = "html-validate";
12420
- const version = "10.9.0";
12496
+ const version = "10.11.0";
12421
12497
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12422
12498
 
12423
12499
  function freeze(src) {
@@ -12433,7 +12509,13 @@ function isThenableArray(value) {
12433
12509
  return isThenable(value[0]);
12434
12510
  }
12435
12511
  class Reporter {
12512
+ /**
12513
+ * @internal
12514
+ */
12436
12515
  result;
12516
+ /**
12517
+ * @internal
12518
+ */
12437
12519
  constructor() {
12438
12520
  this.result = {};
12439
12521
  }
@@ -12446,16 +12528,16 @@ class Reporter {
12446
12528
  }
12447
12529
  const valid = reports.every((report) => report.valid);
12448
12530
  const merged = {};
12449
- reports.forEach((report) => {
12450
- report.results.forEach((result) => {
12531
+ for (const report of reports) {
12532
+ for (const result of report.results) {
12451
12533
  const key = result.filePath;
12452
12534
  if (key in merged) {
12453
12535
  merged[key].messages = [...merged[key].messages, ...result.messages];
12454
12536
  } else {
12455
12537
  merged[key] = { ...result };
12456
12538
  }
12457
- });
12458
- });
12539
+ }
12540
+ }
12459
12541
  const results = Object.values(merged).map((result) => {
12460
12542
  result.errorCount = countErrors(result.messages);
12461
12543
  result.warningCount = countWarnings(result.messages);
@@ -12468,8 +12550,11 @@ class Reporter {
12468
12550
  warningCount: sumWarnings(results)
12469
12551
  };
12470
12552
  }
12471
- /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12472
- add(rule, message, severity, node, location, context) {
12553
+ /**
12554
+ * @internal
12555
+ */
12556
+ add(options) {
12557
+ const { rule, message, severity, node, location, context } = options;
12473
12558
  if (!(location.filename in this.result)) {
12474
12559
  this.result[location.filename] = [];
12475
12560
  }
@@ -12494,17 +12579,23 @@ class Reporter {
12494
12579
  }
12495
12580
  this.result[location.filename].push(entry);
12496
12581
  }
12582
+ /**
12583
+ * @internal
12584
+ */
12497
12585
  addManual(filename, message) {
12498
12586
  if (!(filename in this.result)) {
12499
12587
  this.result[filename] = [];
12500
12588
  }
12501
12589
  this.result[filename].push(message);
12502
12590
  }
12591
+ /**
12592
+ * @internal
12593
+ */
12503
12594
  save(sources) {
12504
12595
  const report = {
12505
12596
  valid: this.isValid(),
12506
12597
  results: Object.keys(this.result).map((filePath) => {
12507
- const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
12598
+ const messages = Array.from(this.result[filePath], freeze).toSorted(messageSort);
12508
12599
  const source = (sources ?? []).find((source2) => filePath === source2.filename);
12509
12600
  return {
12510
12601
  filePath,
@@ -12521,6 +12612,9 @@ class Reporter {
12521
12612
  report.warningCount = sumWarnings(report.results);
12522
12613
  return report;
12523
12614
  }
12615
+ /**
12616
+ * @internal
12617
+ */
12524
12618
  isValid() {
12525
12619
  const numErrors = Object.values(this.result).reduce((sum, messages) => {
12526
12620
  return sum + countErrors(messages);
@@ -12583,6 +12677,7 @@ class ParserError extends Error {
12583
12677
  location;
12584
12678
  constructor(location, message) {
12585
12679
  super(message);
12680
+ this.name = "ParserError";
12586
12681
  this.location = location;
12587
12682
  }
12588
12683
  }
@@ -12739,7 +12834,7 @@ class Parser {
12739
12834
  const tokens = Array.from(
12740
12835
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location)
12741
12836
  );
12742
- const endToken = tokens.slice(-1)[0];
12837
+ const endToken = tokens.at(-1);
12743
12838
  const closeOptional = this.closeOptional(startToken);
12744
12839
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
12745
12840
  const node = HtmlElement.fromTokens(
@@ -12857,7 +12952,7 @@ class Parser {
12857
12952
  const endTokens = Array.from(
12858
12953
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location)
12859
12954
  );
12860
- endToken = endTokens.slice(-1)[0];
12955
+ endToken = endTokens.at(-1);
12861
12956
  const selfClosed = endToken.data[0] === "/>";
12862
12957
  if (tagClosed) {
12863
12958
  startToken = last;
@@ -13139,7 +13234,7 @@ class Parser {
13139
13234
  this.event.once("*", cb);
13140
13235
  }
13141
13236
  trigger(event, data) {
13142
- if (typeof data.location === "undefined") {
13237
+ if (data.location === void 0) {
13143
13238
  throw new Error("Triggered event must contain location");
13144
13239
  }
13145
13240
  this.event.trigger(event, data);
@@ -13267,7 +13362,9 @@ class Engine {
13267
13362
  }
13268
13363
  lines.push({ event, data });
13269
13364
  });
13270
- source.forEach((src) => parser.parseHtml(src));
13365
+ for (const src of source) {
13366
+ parser.parseHtml(src);
13367
+ }
13271
13368
  return lines;
13272
13369
  }
13273
13370
  dumpTokens(source) {
@@ -13732,7 +13829,7 @@ const entities = {
13732
13829
  "&": "&amp;"
13733
13830
  };
13734
13831
  function xmlescape(src) {
13735
- return src.toString().replace(/[><'"&]/g, (match) => {
13832
+ return src.toString().replaceAll(/[><'"&]/g, (match) => {
13736
13833
  return entities[match];
13737
13834
  });
13738
13835
  }
@@ -13752,11 +13849,11 @@ function checkstyleFormatter(results) {
13752
13849
  `;
13753
13850
  output += `<checkstyle version="4.3">
13754
13851
  `;
13755
- results.forEach((result) => {
13852
+ for (const result of results) {
13756
13853
  const messages = result.messages;
13757
13854
  output += ` <file name="${xmlescape(result.filePath)}">
13758
13855
  `;
13759
- messages.forEach((message) => {
13856
+ for (const message of messages) {
13760
13857
  const ruleId = xmlescape(`htmlvalidate.rules.${message.ruleId}`);
13761
13858
  output += " ";
13762
13859
  output += [
@@ -13767,9 +13864,9 @@ function checkstyleFormatter(results) {
13767
13864
  `source="${ruleId}" />`
13768
13865
  ].join(" ");
13769
13866
  output += "\n";
13770
- });
13867
+ }
13771
13868
  output += " </file>\n";
13772
- });
13869
+ }
13773
13870
  output += "</checkstyle>\n";
13774
13871
  return output;
13775
13872
  }
@@ -13839,11 +13936,11 @@ function codeFrameColumns(rawLines, loc) {
13839
13936
  if (hasMarker) {
13840
13937
  let markerLine = "";
13841
13938
  if (Array.isArray(hasMarker)) {
13842
- const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
13939
+ const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replaceAll(/[^\t]/g, " ");
13843
13940
  const numberOfMarkers = hasMarker[1] || 1;
13844
13941
  markerLine = [
13845
13942
  "\n ",
13846
- gutter.replace(/\d/g, " "),
13943
+ gutter.replaceAll(/\d/g, " "),
13847
13944
  " ",
13848
13945
  markerSpacing,
13849
13946
  "^".repeat(numberOfMarkers)
@@ -13981,10 +14078,10 @@ function stylish(results) {
13981
14078
  function textFormatter(results) {
13982
14079
  let output = "";
13983
14080
  let total = 0;
13984
- results.forEach((result) => {
14081
+ for (const result of results) {
13985
14082
  const messages = result.messages;
13986
14083
  if (messages.length === 0) {
13987
- return;
14084
+ continue;
13988
14085
  }
13989
14086
  total += messages.length;
13990
14087
  output += messages.map((message) => {
@@ -14000,7 +14097,7 @@ function textFormatter(results) {
14000
14097
  return `${location}: ${messageType} [${message.ruleId}] ${message.message}
14001
14098
  `;
14002
14099
  }).join("");
14003
- });
14100
+ }
14004
14101
  return total > 0 ? output : "";
14005
14102
  }
14006
14103
  const formatter = textFormatter;
@@ -14832,7 +14929,7 @@ var ignoreExports = /*@__PURE__*/ requireIgnore();
14832
14929
  var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14833
14930
 
14834
14931
  const engines = {
14835
- node: "^20.19.0 || >= 22.12.0"
14932
+ node: "^20.19.0 || >= 22.16.0"
14836
14933
  };
14837
14934
 
14838
14935
  var workerPath = "./jest-worker.js";