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/cjs/core.js CHANGED
@@ -404,6 +404,7 @@ function stringify(value) {
404
404
  class WrappedError extends Error {
405
405
  constructor(message) {
406
406
  super(stringify(message));
407
+ this.name = "WrappedError";
407
408
  }
408
409
  }
409
410
 
@@ -418,8 +419,7 @@ function ensureError(value) {
418
419
  class NestedError extends Error {
419
420
  constructor(message, nested) {
420
421
  super(message);
421
- Error.captureStackTrace(this, NestedError);
422
- this.name = NestedError.name;
422
+ this.name = "NestedError";
423
423
  if (nested?.stack) {
424
424
  this.stack ??= "";
425
425
  this.stack += `
@@ -431,8 +431,7 @@ Caused by: ${nested.stack}`;
431
431
  class UserError extends NestedError {
432
432
  constructor(message, nested) {
433
433
  super(message, nested);
434
- Error.captureStackTrace(this, UserError);
435
- this.name = UserError.name;
434
+ this.name = "UserError";
436
435
  Object.defineProperty(this, "isUserError", {
437
436
  value: true,
438
437
  enumerable: false,
@@ -455,8 +454,7 @@ class InheritError extends UserError {
455
454
  constructor({ tagName, inherit }) {
456
455
  const message = `Element <${tagName}> cannot inherit from <${inherit}>: no such element`;
457
456
  super(message);
458
- Error.captureStackTrace(this, InheritError);
459
- this.name = InheritError.name;
457
+ this.name = "InheritError";
460
458
  this.tagName = tagName;
461
459
  this.inherit = inherit;
462
460
  this.filename = null;
@@ -498,6 +496,7 @@ class SchemaValidationError extends UserError {
498
496
  constructor(filename, message, obj, schema, errors) {
499
497
  const summary = getSummary(schema, obj, errors);
500
498
  super(`${message}: ${summary}`);
499
+ this.name = "SchemaValidationError";
501
500
  this.filename = filename;
502
501
  this.obj = obj;
503
502
  this.schema = schema;
@@ -645,6 +644,18 @@ const patternProperties = {
645
644
  }
646
645
  ]
647
646
  },
647
+ submitButton: {
648
+ title: "Mark this element as a submit button",
649
+ description: "This element can be used to submit forms.",
650
+ anyOf: [
651
+ {
652
+ type: "boolean"
653
+ },
654
+ {
655
+ "function": true
656
+ }
657
+ ]
658
+ },
648
659
  templateRoot: {
649
660
  title: "Mark element as an element ignoring DOM ancestry, i.e. <template>.",
650
661
  description: "The <template> element can contain any elements.",
@@ -1028,6 +1039,7 @@ const MetaCopyableProperty = [
1028
1039
  "form",
1029
1040
  "formAssociated",
1030
1041
  "labelable",
1042
+ "submitButton",
1031
1043
  "attributes",
1032
1044
  "aria",
1033
1045
  "permittedContent",
@@ -1042,7 +1054,7 @@ function setMetaProperty(dst, key, value) {
1042
1054
  }
1043
1055
 
1044
1056
  function isSet(value) {
1045
- return typeof value !== "undefined";
1057
+ return value !== void 0;
1046
1058
  }
1047
1059
  function flag(value) {
1048
1060
  return value ? true : void 0;
@@ -1057,7 +1069,7 @@ function migrateSingleAttribute(src, key) {
1057
1069
  result.required = flag(src.requiredAttributes?.includes(key));
1058
1070
  result.omit = void 0;
1059
1071
  const attr = src.attributes ? src.attributes[key] : void 0;
1060
- if (typeof attr === "undefined") {
1072
+ if (attr === void 0) {
1061
1073
  return stripUndefined(result);
1062
1074
  }
1063
1075
  if (attr === null) {
@@ -1084,7 +1096,7 @@ function migrateAttributes(src) {
1084
1096
  ...src.requiredAttributes ?? [],
1085
1097
  ...src.deprecatedAttributes ?? []
1086
1098
  /* eslint-disable-next-line sonarjs/no-alphabetical-sort -- not really needed in this case, this is a-z anyway */
1087
- ].sort();
1099
+ ].toSorted();
1088
1100
  const entries = keys.map((key) => {
1089
1101
  return [key, migrateSingleAttribute(src, key)];
1090
1102
  });
@@ -1112,9 +1124,7 @@ function migrateElement(src) {
1112
1124
  const implicitRole = normalizeAriaImplicitRole(src.implicitRole ?? src.aria?.implicitRole);
1113
1125
  const result = {
1114
1126
  ...src,
1115
- ...{
1116
- formAssociated: void 0
1117
- },
1127
+ formAssociated: void 0,
1118
1128
  attributes: migrateAttributes(src),
1119
1129
  textContent: src.textContent,
1120
1130
  focusable: src.focusable ?? false,
@@ -1149,7 +1159,8 @@ const dynamicKeys = [
1149
1159
  "phrasing",
1150
1160
  "embedded",
1151
1161
  "interactive",
1152
- "labelable"
1162
+ "labelable",
1163
+ "submitButton"
1153
1164
  ];
1154
1165
  const schemaCache = /* @__PURE__ */ new Map();
1155
1166
  function clone(src) {
@@ -1419,13 +1430,10 @@ class Attribute {
1419
1430
  */
1420
1431
  constructor(key, value, keyLocation, valueLocation, originalAttribute) {
1421
1432
  this.key = key;
1422
- this.value = value;
1433
+ this.value = value ?? null;
1423
1434
  this.keyLocation = keyLocation;
1424
1435
  this.valueLocation = valueLocation;
1425
1436
  this.originalAttribute = originalAttribute;
1426
- if (typeof this.value === "undefined") {
1427
- this.value = null;
1428
- }
1429
1437
  }
1430
1438
  /**
1431
1439
  * Flag set to true if the attribute value is static.
@@ -1569,11 +1577,11 @@ class Context {
1569
1577
  while ((offset = consumed.indexOf("\n")) >= 0) {
1570
1578
  this.line++;
1571
1579
  this.column = 1;
1572
- consumed = consumed.substr(offset + 1);
1580
+ consumed = consumed.slice(offset + 1);
1573
1581
  }
1574
1582
  this.column += consumed.length;
1575
1583
  this.offset += n;
1576
- this.string = this.string.substr(n);
1584
+ this.string = this.string.slice(n);
1577
1585
  this.state = state;
1578
1586
  }
1579
1587
  getLocation(size) {
@@ -1751,7 +1759,7 @@ class DOMNode {
1751
1759
  * node has no children.
1752
1760
  */
1753
1761
  get lastChild() {
1754
- return this.childNodes[this.childNodes.length - 1] || null;
1762
+ return this.childNodes.at(-1) ?? null;
1755
1763
  }
1756
1764
  /**
1757
1765
  * @internal
@@ -1845,7 +1853,7 @@ class DOMNode {
1845
1853
  }
1846
1854
  _removeChild(node) {
1847
1855
  const index = this.childNodes.findIndex((it) => it.isSameNode(node));
1848
- if (index >= 0) {
1856
+ if (index !== -1) {
1849
1857
  this.childNodes.splice(index, 1);
1850
1858
  } else {
1851
1859
  throw new Error("DOMException: _removeChild(..) could not find child to remove");
@@ -1866,7 +1874,7 @@ function parse(text, baseLocation) {
1866
1874
  begin++;
1867
1875
  continue;
1868
1876
  }
1869
- const token = text.substring(begin, end);
1877
+ const token = text.slice(begin, end);
1870
1878
  tokens.push(token);
1871
1879
  if (locations && baseLocation) {
1872
1880
  const location = sliceLocation(baseLocation, begin, end);
@@ -1881,7 +1889,7 @@ class DOMTokenList extends Array {
1881
1889
  locations;
1882
1890
  constructor(value, location) {
1883
1891
  if (value && typeof value === "string") {
1884
- const normalized = value.replace(/[\t\r\n]/g, " ");
1892
+ const normalized = value.replaceAll(/[\t\r\n]/g, " ");
1885
1893
  const { tokens, locations } = parse(normalized, location);
1886
1894
  super(...tokens);
1887
1895
  this.locations = locations;
@@ -1971,7 +1979,7 @@ function nthChild(node, args) {
1971
1979
  if (!args) {
1972
1980
  throw new Error("Missing argument to nth-child");
1973
1981
  }
1974
- const n = parseInt(args.trim(), 10);
1982
+ const n = Number.parseInt(args.trim(), 10);
1975
1983
  const cur = getNthChild(node);
1976
1984
  return cur === n;
1977
1985
  }
@@ -1996,7 +2004,7 @@ function factory(name, context) {
1996
2004
  }
1997
2005
 
1998
2006
  function stripslashes(value) {
1999
- return value.replace(/\\(.)/g, "$1");
2007
+ return value.replaceAll(/\\(.)/g, "$1");
2000
2008
  }
2001
2009
  class Condition {
2002
2010
  }
@@ -2195,7 +2203,7 @@ function candidatesFromCombinator(element, combinator) {
2195
2203
  }
2196
2204
  }
2197
2205
  function matchElement(element, compounds, context) {
2198
- const last = compounds[compounds.length - 1];
2206
+ const last = compounds.at(-1);
2199
2207
  if (!last.match(element, context)) {
2200
2208
  return false;
2201
2209
  }
@@ -2212,7 +2220,7 @@ function matchElement(element, compounds, context) {
2212
2220
  return false;
2213
2221
  }
2214
2222
 
2215
- const escapedCodepoints = ["9", "a", "d"];
2223
+ const escapedCodepoints = /* @__PURE__ */ new Set(["9", "a", "d"]);
2216
2224
  function* splitSelectorElements(selector) {
2217
2225
  let begin = 0;
2218
2226
  let end = 0;
@@ -2227,7 +2235,7 @@ function* splitSelectorElements(selector) {
2227
2235
  return 0 /* INITIAL */;
2228
2236
  }
2229
2237
  function escapedState(ch) {
2230
- if (escapedCodepoints.includes(ch)) {
2238
+ if (escapedCodepoints.has(ch)) {
2231
2239
  return 1 /* ESCAPED */;
2232
2240
  }
2233
2241
  return 0 /* INITIAL */;
@@ -2267,7 +2275,7 @@ function unescapeCodepoint(value) {
2267
2275
  "\\a ": "\n",
2268
2276
  "\\d ": "\r"
2269
2277
  };
2270
- return value.replace(
2278
+ return value.replaceAll(
2271
2279
  /(\\[\u0039\u0061\u0064] )/g,
2272
2280
  (_, codepoint) => replacement[codepoint]
2273
2281
  );
@@ -2278,7 +2286,7 @@ function escapeSelectorComponent(text) {
2278
2286
  "\n": "\\a ",
2279
2287
  "\r": "\\d "
2280
2288
  };
2281
- return text.toString().replace(/([\t\n\r]|[^a-z0-9_-])/gi, (_, ch) => {
2289
+ return text.toString().replaceAll(/([\t\n\r]|[^a-z0-9_-])/gi, (_, ch) => {
2282
2290
  if (codepoints[ch]) {
2283
2291
  return codepoints[ch];
2284
2292
  } else {
@@ -2327,7 +2335,7 @@ class Selector {
2327
2335
  }
2328
2336
  }
2329
2337
  static parse(selector) {
2330
- selector = selector.replace(/([+~>]) /g, "$1");
2338
+ selector = selector.replaceAll(/([+~>]) /g, "$1");
2331
2339
  return Array.from(splitSelectorElements(selector), (element) => {
2332
2340
  return new Compound(unescapeCodepoint(element));
2333
2341
  });
@@ -2568,7 +2576,7 @@ class HtmlElement extends DOMNode {
2568
2576
  return attr.value;
2569
2577
  }
2570
2578
  const list = new DOMTokenList(attr.value, attr.valueLocation);
2571
- return list.length ? Array.from(list) : null;
2579
+ return list.length > 0 ? Array.from(list) : null;
2572
2580
  }
2573
2581
  /**
2574
2582
  * Similar to childNodes but only elements.
@@ -2623,7 +2631,7 @@ class HtmlElement extends DOMNode {
2623
2631
  }
2624
2632
  parts.push(`${cur.tagName.toLowerCase()}:nth-child(${String(index + 1)})`);
2625
2633
  }
2626
- return parts.reverse().join(" > ");
2634
+ return parts.toReversed().join(" > ");
2627
2635
  }
2628
2636
  /**
2629
2637
  * Tests if this element has given tagname.
@@ -2665,7 +2673,7 @@ class HtmlElement extends DOMNode {
2665
2673
  this.metaElement ??= {};
2666
2674
  for (const key of MetaCopyableProperty) {
2667
2675
  const value = meta[key];
2668
- if (typeof value !== "undefined") {
2676
+ if (value !== void 0) {
2669
2677
  setMetaProperty(this.metaElement, key, value);
2670
2678
  } else {
2671
2679
  delete this.metaElement[key];
@@ -2769,8 +2777,8 @@ class HtmlElement extends DOMNode {
2769
2777
  if (tabindex.value instanceof DynamicValue) {
2770
2778
  return this.cacheSet(TABINDEX, 0);
2771
2779
  }
2772
- const parsed = parseInt(tabindex.value, 10);
2773
- if (isNaN(parsed)) {
2780
+ const parsed = Number.parseInt(tabindex.value, 10);
2781
+ if (Number.isNaN(parsed)) {
2774
2782
  return this.cacheSet(TABINDEX, null);
2775
2783
  }
2776
2784
  return this.cacheSet(TABINDEX, parsed);
@@ -2874,7 +2882,10 @@ class HtmlElement extends DOMNode {
2874
2882
  */
2875
2883
  get lastElementChild() {
2876
2884
  const children = this.childElements;
2877
- return children.length > 0 ? children[children.length - 1] : null;
2885
+ return children.length > 0 ? (
2886
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it is checked right before this */
2887
+ children.at(-1)
2888
+ ) : null;
2878
2889
  }
2879
2890
  get siblings() {
2880
2891
  return this.parent ? this.parent.childElements : [this];
@@ -2997,7 +3008,9 @@ function depthFirst(root, callback) {
2997
3008
  root = root.root;
2998
3009
  }
2999
3010
  function visit(node) {
3000
- node.childElements.forEach(visit);
3011
+ for (const child of node.childElements) {
3012
+ visit(child);
3013
+ }
3001
3014
  if (!node.isRootElement()) {
3002
3015
  callback(node);
3003
3016
  }
@@ -3086,7 +3099,7 @@ class DOMTree {
3086
3099
  }
3087
3100
  }
3088
3101
 
3089
- const allowedKeys = ["exclude"];
3102
+ const allowedKeys = /* @__PURE__ */ new Set(["exclude"]);
3090
3103
  class Validator {
3091
3104
  /**
3092
3105
  * Test if element is used in a proper context.
@@ -3325,7 +3338,7 @@ class Validator {
3325
3338
  }
3326
3339
  function validateKeys(rule) {
3327
3340
  for (const key of Object.keys(rule)) {
3328
- if (!allowedKeys.includes(key)) {
3341
+ if (!allowedKeys.has(key)) {
3329
3342
  const str = JSON.stringify(rule);
3330
3343
  throw new Error(`Permitted rule "${str}" contains unknown property "${key}"`);
3331
3344
  }
@@ -3367,7 +3380,7 @@ function parseSeverity(value) {
3367
3380
 
3368
3381
  const cacheKey = /* @__PURE__ */ Symbol("aria-naming");
3369
3382
  const defaultValue = "allowed";
3370
- const prohibitedRoles = [
3383
+ const prohibitedRoles = /* @__PURE__ */ new Set([
3371
3384
  "caption",
3372
3385
  "code",
3373
3386
  "deletion",
@@ -3379,9 +3392,9 @@ const prohibitedRoles = [
3379
3392
  "strong",
3380
3393
  "subscript",
3381
3394
  "superscript"
3382
- ];
3395
+ ]);
3383
3396
  function byRole(role) {
3384
- return prohibitedRoles.includes(role) ? "prohibited" : "allowed";
3397
+ return prohibitedRoles.has(role) ? "prohibited" : "allowed";
3385
3398
  }
3386
3399
  function byMeta(element, meta) {
3387
3400
  return meta.aria.naming(element._adapter);
@@ -3433,7 +3446,7 @@ function isInputDisabledImpl(node) {
3433
3446
 
3434
3447
  const patternCache = /* @__PURE__ */ new Map();
3435
3448
  function compileStringPattern(pattern) {
3436
- const regexp = pattern.replace(/[*]+/g, ".+");
3449
+ const regexp = pattern.replaceAll(/[*]+/g, ".+");
3437
3450
  return new RegExp(`^${regexp}$`);
3438
3451
  }
3439
3452
  function compileRegExpPattern(pattern) {
@@ -3708,8 +3721,8 @@ function format(value, quote = false) {
3708
3721
  return String(value);
3709
3722
  }
3710
3723
  function interpolate(text, data) {
3711
- return text.replace(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
3712
- return typeof data[key] !== "undefined" ? format(data[key]) : match;
3724
+ return text.replaceAll(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
3725
+ return data[key] !== void 0 ? format(data[key]) : match;
3713
3726
  });
3714
3727
  }
3715
3728
 
@@ -3917,7 +3930,14 @@ class Rule {
3917
3930
  });
3918
3931
  if (enabled && !blocked) {
3919
3932
  const interpolated = interpolate(message, context ?? {});
3920
- this.reporter.add(this, interpolated, this.severity, node, where, context);
3933
+ this.reporter.add({
3934
+ rule: this,
3935
+ message: interpolated,
3936
+ severity: this.severity,
3937
+ node,
3938
+ location: where,
3939
+ context
3940
+ });
3921
3941
  }
3922
3942
  }
3923
3943
  findLocation(src) {
@@ -4008,7 +4028,7 @@ class Rule {
4008
4028
  }
4009
4029
  }
4010
4030
 
4011
- const defaults$B = {
4031
+ const defaults$C = {
4012
4032
  allowExternal: true,
4013
4033
  allowRelative: true,
4014
4034
  allowAbsolute: true,
@@ -4052,7 +4072,7 @@ class AllowedLinks extends Rule {
4052
4072
  allowRelative;
4053
4073
  allowAbsolute;
4054
4074
  constructor(options) {
4055
- super({ ...defaults$B, ...options });
4075
+ super({ ...defaults$C, ...options });
4056
4076
  this.allowExternal = parseAllow(this.options.allowExternal);
4057
4077
  this.allowRelative = parseAllow(this.options.allowRelative);
4058
4078
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4220,7 +4240,7 @@ class AllowedLinks extends Rule {
4220
4240
  }
4221
4241
  }
4222
4242
 
4223
- const defaults$A = {
4243
+ const defaults$B = {
4224
4244
  accessible: true
4225
4245
  };
4226
4246
  function findByTarget(target, siblings) {
@@ -4250,7 +4270,7 @@ function getDescription$1(context) {
4250
4270
  }
4251
4271
  class AreaAlt extends Rule {
4252
4272
  constructor(options) {
4253
- super({ ...defaults$A, ...options });
4273
+ super({ ...defaults$B, ...options });
4254
4274
  }
4255
4275
  static schema() {
4256
4276
  return {
@@ -4329,7 +4349,7 @@ class AriaHiddenBody extends Rule {
4329
4349
  }
4330
4350
  }
4331
4351
 
4332
- const defaults$z = {
4352
+ const defaults$A = {
4333
4353
  allowAnyNamable: false,
4334
4354
  elements: {
4335
4355
  include: null,
@@ -4377,7 +4397,7 @@ function isValidUsage(target, meta) {
4377
4397
  }
4378
4398
  class AriaLabelMisuse extends Rule {
4379
4399
  constructor(options) {
4380
- super({ ...defaults$z, ...options });
4400
+ super({ ...defaults$A, ...options });
4381
4401
  }
4382
4402
  static schema() {
4383
4403
  return {
@@ -4489,8 +4509,7 @@ class AriaLabelMisuse extends Rule {
4489
4509
  class ConfigError extends UserError {
4490
4510
  constructor(message, nested) {
4491
4511
  super(message, nested);
4492
- Error.captureStackTrace(this, ConfigError);
4493
- this.name = ConfigError.name;
4512
+ this.name = "ConfigError";
4494
4513
  }
4495
4514
  }
4496
4515
 
@@ -4546,14 +4565,14 @@ class CaseStyle {
4546
4565
  }
4547
4566
  }
4548
4567
 
4549
- const defaults$y = {
4568
+ const defaults$z = {
4550
4569
  style: "lowercase",
4551
4570
  ignoreForeign: true
4552
4571
  };
4553
4572
  class AttrCase extends Rule {
4554
4573
  style;
4555
4574
  constructor(options) {
4556
- super({ ...defaults$y, ...options });
4575
+ super({ ...defaults$z, ...options });
4557
4576
  this.style = new CaseStyle(this.options.style, "attr-case");
4558
4577
  }
4559
4578
  static schema() {
@@ -4594,7 +4613,7 @@ class AttrCase extends Rule {
4594
4613
  if (event.originalAttribute) {
4595
4614
  return;
4596
4615
  }
4597
- const letters = event.key.replace(/[^a-z]+/gi, "");
4616
+ const letters = event.key.replaceAll(/[^a-z]+/gi, "");
4598
4617
  if (this.style.match(letters)) {
4599
4618
  return;
4600
4619
  }
@@ -4667,6 +4686,7 @@ class InvalidTokenError extends Error {
4667
4686
  location;
4668
4687
  constructor(location, message) {
4669
4688
  super(message);
4689
+ this.name = "InvalidTokenError";
4670
4690
  this.location = location;
4671
4691
  }
4672
4692
  }
@@ -4776,16 +4796,26 @@ class Lexer {
4776
4796
  */
4777
4797
  enter(context, state, data) {
4778
4798
  if (state === State.TAG && data?.[0].startsWith("<")) {
4779
- if (data[0] === "<script") {
4780
- context.contentModel = ContentModel.SCRIPT;
4781
- } else if (data[0] === "<style") {
4782
- context.contentModel = ContentModel.STYLE;
4783
- } else if (data[0] === "<textarea") {
4784
- context.contentModel = ContentModel.TEXTAREA;
4785
- } else if (data[0] === "<title") {
4786
- context.contentModel = ContentModel.TITLE;
4787
- } else {
4788
- context.contentModel = ContentModel.TEXT;
4799
+ switch (data[0]) {
4800
+ case "<script": {
4801
+ context.contentModel = ContentModel.SCRIPT;
4802
+ break;
4803
+ }
4804
+ case "<style": {
4805
+ context.contentModel = ContentModel.STYLE;
4806
+ break;
4807
+ }
4808
+ case "<textarea": {
4809
+ context.contentModel = ContentModel.TEXTAREA;
4810
+ break;
4811
+ }
4812
+ case "<title": {
4813
+ context.contentModel = ContentModel.TITLE;
4814
+ break;
4815
+ }
4816
+ default: {
4817
+ context.contentModel = ContentModel.TEXT;
4818
+ }
4789
4819
  }
4790
4820
  }
4791
4821
  }
@@ -4958,7 +4988,7 @@ class AttrDelimiter extends Rule {
4958
4988
  }
4959
4989
 
4960
4990
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4961
- const defaults$x = {
4991
+ const defaults$y = {
4962
4992
  pattern: DEFAULT_PATTERN,
4963
4993
  ignoreForeign: true
4964
4994
  };
@@ -4991,7 +5021,7 @@ function generateDescription(name, pattern) {
4991
5021
  class AttrPattern extends Rule {
4992
5022
  pattern;
4993
5023
  constructor(options) {
4994
- super({ ...defaults$x, ...options });
5024
+ super({ ...defaults$y, ...options });
4995
5025
  this.pattern = generateRegexp(this.options.pattern);
4996
5026
  }
4997
5027
  static schema() {
@@ -5038,7 +5068,7 @@ class AttrPattern extends Rule {
5038
5068
  }
5039
5069
  }
5040
5070
 
5041
- const defaults$w = {
5071
+ const defaults$x = {
5042
5072
  style: "auto",
5043
5073
  unquoted: false
5044
5074
  };
@@ -5104,7 +5134,7 @@ class AttrQuotes extends Rule {
5104
5134
  };
5105
5135
  }
5106
5136
  constructor(options) {
5107
- super({ ...defaults$w, ...options });
5137
+ super({ ...defaults$x, ...options });
5108
5138
  this.style = parseStyle$3(this.options.style);
5109
5139
  }
5110
5140
  setup() {
@@ -5186,10 +5216,10 @@ class AttrSpacing extends Rule {
5186
5216
 
5187
5217
  function pick(attr) {
5188
5218
  const result = {};
5189
- if (typeof attr.enum !== "undefined") {
5219
+ if (attr.enum !== void 0) {
5190
5220
  result.enum = attr.enum;
5191
5221
  }
5192
- if (typeof attr.boolean !== "undefined") {
5222
+ if (attr.boolean !== void 0) {
5193
5223
  result.boolean = attr.boolean;
5194
5224
  }
5195
5225
  return result;
@@ -5260,13 +5290,13 @@ class AttributeAllowedValues extends Rule {
5260
5290
  }
5261
5291
  }
5262
5292
 
5263
- const defaults$v = {
5293
+ const defaults$w = {
5264
5294
  style: "omit"
5265
5295
  };
5266
5296
  class AttributeBooleanStyle extends Rule {
5267
5297
  hasInvalidStyle;
5268
5298
  constructor(options) {
5269
- super({ ...defaults$v, ...options });
5299
+ super({ ...defaults$w, ...options });
5270
5300
  this.hasInvalidStyle = parseStyle$2(this.options.style);
5271
5301
  }
5272
5302
  static schema() {
@@ -5336,13 +5366,13 @@ function reportMessage$1(attr, style) {
5336
5366
  return "";
5337
5367
  }
5338
5368
 
5339
- const defaults$u = {
5369
+ const defaults$v = {
5340
5370
  style: "omit"
5341
5371
  };
5342
5372
  class AttributeEmptyStyle extends Rule {
5343
5373
  hasInvalidStyle;
5344
5374
  constructor(options) {
5345
- super({ ...defaults$u, ...options });
5375
+ super({ ...defaults$v, ...options });
5346
5376
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5347
5377
  }
5348
5378
  static schema() {
@@ -5459,7 +5489,7 @@ class AttributeMisuse extends Rule {
5459
5489
  }
5460
5490
  }
5461
5491
 
5462
- const defaults$t = {
5492
+ const defaults$u = {
5463
5493
  preferred: void 0
5464
5494
  };
5465
5495
  function isPasswordInput(event) {
@@ -5476,7 +5506,7 @@ function isGroupingToken(token) {
5476
5506
  class AutocompletePassword extends Rule {
5477
5507
  preferred;
5478
5508
  constructor(options) {
5479
- super({ ...defaults$t, ...options });
5509
+ super({ ...defaults$u, ...options });
5480
5510
  this.preferred = options.preferred?.toLowerCase();
5481
5511
  }
5482
5512
  static schema() {
@@ -5555,21 +5585,19 @@ class AutocompletePassword extends Rule {
5555
5585
  });
5556
5586
  return;
5557
5587
  }
5558
- if (preferred) {
5559
- if (value !== preferred) {
5560
- const context = {
5561
- kind: "preferred-mismatch",
5562
- value,
5563
- preferred
5564
- };
5565
- this.report({
5566
- node: target,
5567
- message: `<input type="password"> should use autocomplete="${preferred}"`,
5568
- /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- location must be present if value is */
5569
- location,
5570
- context
5571
- });
5572
- }
5588
+ if (preferred && value !== preferred) {
5589
+ const context = {
5590
+ kind: "preferred-mismatch",
5591
+ value,
5592
+ preferred
5593
+ };
5594
+ this.report({
5595
+ node: target,
5596
+ message: `<input type="password"> should use autocomplete="${preferred}"`,
5597
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- location must be present if value is */
5598
+ location,
5599
+ context
5600
+ });
5573
5601
  }
5574
5602
  });
5575
5603
  }
@@ -5706,7 +5734,7 @@ class BasePatternRule extends Rule {
5706
5734
  }
5707
5735
  }
5708
5736
 
5709
- const defaults$s = {
5737
+ const defaults$t = {
5710
5738
  pattern: "kebabcase"
5711
5739
  };
5712
5740
  class ClassPattern extends BasePatternRule {
@@ -5714,7 +5742,7 @@ class ClassPattern extends BasePatternRule {
5714
5742
  super({
5715
5743
  ruleId: "class-pattern",
5716
5744
  attr: "class",
5717
- options: { ...defaults$s, ...options },
5745
+ options: { ...defaults$t, ...options },
5718
5746
  allowedPatterns: patternNames
5719
5747
  // allow all patterns
5720
5748
  });
@@ -5864,13 +5892,13 @@ class CloseOrder extends Rule {
5864
5892
  }
5865
5893
  }
5866
5894
 
5867
- const defaults$r = {
5895
+ const defaults$s = {
5868
5896
  include: null,
5869
5897
  exclude: null
5870
5898
  };
5871
5899
  class Deprecated extends Rule {
5872
5900
  constructor(options) {
5873
- super({ ...defaults$r, ...options });
5901
+ super({ ...defaults$s, ...options });
5874
5902
  }
5875
5903
  static schema() {
5876
5904
  return {
@@ -5916,7 +5944,7 @@ class Deprecated extends Rule {
5916
5944
  text.push(context.documentation);
5917
5945
  }
5918
5946
  const doc = {
5919
- description: text.map((cur) => cur.replace(/\$tagname/g, context.tagName)).join("\n\n"),
5947
+ description: text.map((cur) => cur.replaceAll("$tagname", context.tagName)).join("\n\n"),
5920
5948
  url: "https://html-validate.org/rules/deprecated.html"
5921
5949
  };
5922
5950
  return doc;
@@ -5982,7 +6010,7 @@ function quote(value, char = '"') {
5982
6010
  return `${char}${value}${char}`;
5983
6011
  }
5984
6012
 
5985
- const defaults$q = {
6013
+ const defaults$r = {
5986
6014
  classes: []
5987
6015
  };
5988
6016
  function isRelevant$6(event) {
@@ -6036,7 +6064,7 @@ ${listItems.join("\n")}`);
6036
6064
  class DeprecatedClass extends Rule {
6037
6065
  deprecatedMap;
6038
6066
  constructor(options) {
6039
- super({ ...defaults$q, ...options });
6067
+ super({ ...defaults$r, ...options });
6040
6068
  const { classes } = this.options;
6041
6069
  this.deprecatedMap = new Map(classes.map((entry) => [entry.class, normalizeEntry(entry)]));
6042
6070
  }
@@ -6155,12 +6183,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
6155
6183
  }
6156
6184
  };
6157
6185
 
6158
- const defaults$p = {
6186
+ const defaults$q = {
6159
6187
  style: "uppercase"
6160
6188
  };
6161
6189
  class DoctypeStyle extends Rule {
6162
6190
  constructor(options) {
6163
- super({ ...defaults$p, ...options });
6191
+ super({ ...defaults$q, ...options });
6164
6192
  }
6165
6193
  static schema() {
6166
6194
  return {
@@ -6188,13 +6216,13 @@ class DoctypeStyle extends Rule {
6188
6216
  }
6189
6217
  }
6190
6218
 
6191
- const defaults$o = {
6219
+ const defaults$p = {
6192
6220
  style: "lowercase"
6193
6221
  };
6194
6222
  class ElementCase extends Rule {
6195
6223
  style;
6196
6224
  constructor(options) {
6197
- super({ ...defaults$o, ...options });
6225
+ super({ ...defaults$p, ...options });
6198
6226
  this.style = new CaseStyle(this.options.style, "element-case");
6199
6227
  }
6200
6228
  static schema() {
@@ -6235,7 +6263,7 @@ class ElementCase extends Rule {
6235
6263
  });
6236
6264
  }
6237
6265
  validateCase(target, targetLocation) {
6238
- const letters = target.tagName.replace(/[^a-z]+/gi, "");
6266
+ const letters = target.tagName.replaceAll(/[^a-z]+/gi, "");
6239
6267
  if (!this.style.match(letters)) {
6240
6268
  const location = sliceLocation(targetLocation, 1);
6241
6269
  this.report(target, `Element "${target.tagName}" should be ${this.style.name}`, location);
@@ -6254,7 +6282,7 @@ class ElementCase extends Rule {
6254
6282
  }
6255
6283
  }
6256
6284
 
6257
- const defaults$n = {
6285
+ const defaults$o = {
6258
6286
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
6259
6287
  whitelist: [],
6260
6288
  blacklist: []
@@ -6262,7 +6290,7 @@ const defaults$n = {
6262
6290
  class ElementName extends Rule {
6263
6291
  pattern;
6264
6292
  constructor(options) {
6265
- super({ ...defaults$n, ...options });
6293
+ super({ ...defaults$o, ...options });
6266
6294
  this.pattern = new RegExp(this.options.pattern);
6267
6295
  }
6268
6296
  static schema() {
@@ -6299,7 +6327,7 @@ class ElementName extends Rule {
6299
6327
  ...context.blacklist.map((cur) => `- ${cur}`)
6300
6328
  ];
6301
6329
  }
6302
- if (context.pattern !== defaults$n.pattern) {
6330
+ if (context.pattern !== defaults$o.pattern) {
6303
6331
  return [
6304
6332
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
6305
6333
  "",
@@ -6816,7 +6844,7 @@ class EmptyTitle extends Rule {
6816
6844
  }
6817
6845
  }
6818
6846
 
6819
- const defaults$m = {
6847
+ const defaults$n = {
6820
6848
  allowArrayBrackets: true,
6821
6849
  allowCheckboxDefault: true,
6822
6850
  shared: ["radio", "button", "reset", "submit"]
@@ -6839,8 +6867,8 @@ function haveName(name) {
6839
6867
  return typeof name === "string" && name !== "";
6840
6868
  }
6841
6869
  function allowSharedName(node, shared) {
6842
- const type = node.getAttribute("type");
6843
- return Boolean(type?.valueMatches(shared, false));
6870
+ const type = getControlType(node);
6871
+ return shared.includes(type);
6844
6872
  }
6845
6873
  function isInputHidden(element) {
6846
6874
  return element.is("input") && element.getAttributeValue("type") === "hidden";
@@ -6848,6 +6876,13 @@ function isInputHidden(element) {
6848
6876
  function isInputCheckbox(element) {
6849
6877
  return element.is("input") && element.getAttributeValue("type") === "checkbox";
6850
6878
  }
6879
+ function getControlType(element) {
6880
+ const type = element.getAttributeValue("type") ?? "";
6881
+ if (element.is("button") && type === "") {
6882
+ return "submit";
6883
+ }
6884
+ return type;
6885
+ }
6851
6886
  function isCheckboxWithDefault(control, previous, options) {
6852
6887
  const { allowCheckboxDefault } = options;
6853
6888
  if (!allowCheckboxDefault) {
@@ -6876,7 +6911,7 @@ function getDocumentation(context) {
6876
6911
  }
6877
6912
  class FormDupName extends Rule {
6878
6913
  constructor(options) {
6879
- super({ ...defaults$m, ...options });
6914
+ super({ ...defaults$n, ...options });
6880
6915
  }
6881
6916
  static schema() {
6882
6917
  return {
@@ -6985,7 +7020,7 @@ class FormDupName extends Rule {
6985
7020
  validateSharedName(control, group, attr, name) {
6986
7021
  const uniqueElements = this.getUniqueElements(group);
6987
7022
  const sharedElements = this.getSharedElements(group);
6988
- const type = control.getAttributeValue("type") ?? "";
7023
+ const type = getControlType(control);
6989
7024
  if (uniqueElements.has(name) || sharedElements.has(name) && sharedElements.get(name) !== type) {
6990
7025
  const context = {
6991
7026
  name,
@@ -7035,7 +7070,7 @@ class FormDupName extends Rule {
7035
7070
  }
7036
7071
  }
7037
7072
 
7038
- const defaults$l = {
7073
+ const defaults$m = {
7039
7074
  allowMultipleH1: false,
7040
7075
  minInitialRank: "h1",
7041
7076
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
@@ -7047,7 +7082,7 @@ function isRelevant$5(event) {
7047
7082
  function extractLevel(node) {
7048
7083
  const match = /^[hH](\d)$/.exec(node.tagName);
7049
7084
  if (match) {
7050
- return parseInt(match[1], 10);
7085
+ return Number.parseInt(match[1], 10);
7051
7086
  } else {
7052
7087
  return null;
7053
7088
  }
@@ -7060,14 +7095,14 @@ function parseMaxInitial(value) {
7060
7095
  if (!match) {
7061
7096
  return 1;
7062
7097
  }
7063
- return parseInt(match[1], 10);
7098
+ return Number.parseInt(match[1], 10);
7064
7099
  }
7065
7100
  class HeadingLevel extends Rule {
7066
7101
  minInitialRank;
7067
7102
  sectionRoots;
7068
7103
  stack = [];
7069
7104
  constructor(options) {
7070
- super({ ...defaults$l, ...options });
7105
+ super({ ...defaults$m, ...options });
7071
7106
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
7072
7107
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Compound(it));
7073
7108
  this.stack.push({
@@ -7209,10 +7244,10 @@ class HeadingLevel extends Rule {
7209
7244
  this.stack.pop();
7210
7245
  }
7211
7246
  getPrevRoot() {
7212
- return this.stack[this.stack.length - 2];
7247
+ return this.stack.at(-2);
7213
7248
  }
7214
7249
  getCurrentRoot() {
7215
- return this.stack[this.stack.length - 1];
7250
+ return this.stack.at(-1);
7216
7251
  }
7217
7252
  isSectioningRoot(node) {
7218
7253
  const context = {
@@ -7306,7 +7341,7 @@ class HiddenFocusable extends Rule {
7306
7341
  }
7307
7342
  }
7308
7343
 
7309
- const defaults$k = {
7344
+ const defaults$l = {
7310
7345
  pattern: "kebabcase"
7311
7346
  };
7312
7347
  function exclude$1(set, ...values) {
@@ -7322,7 +7357,7 @@ class IdPattern extends BasePatternRule {
7322
7357
  super({
7323
7358
  ruleId: "id-pattern",
7324
7359
  attr: "id",
7325
- options: { ...defaults$k, ...options },
7360
+ options: { ...defaults$l, ...options },
7326
7361
  allowedPatterns
7327
7362
  });
7328
7363
  }
@@ -7644,13 +7679,13 @@ function findLabelByParent(el) {
7644
7679
  return [];
7645
7680
  }
7646
7681
 
7647
- const defaults$j = {
7682
+ const defaults$k = {
7648
7683
  maxlength: 70
7649
7684
  };
7650
7685
  class LongTitle extends Rule {
7651
7686
  maxlength;
7652
7687
  constructor(options) {
7653
- super({ ...defaults$j, ...options });
7688
+ super({ ...defaults$k, ...options });
7654
7689
  this.maxlength = this.options.maxlength;
7655
7690
  }
7656
7691
  static schema() {
@@ -7754,12 +7789,12 @@ class MapIdName extends Rule {
7754
7789
  }
7755
7790
  }
7756
7791
 
7757
- const defaults$i = {
7792
+ const defaults$j = {
7758
7793
  allowLongDelay: false
7759
7794
  };
7760
7795
  class MetaRefresh extends Rule {
7761
7796
  constructor(options) {
7762
- super({ ...defaults$i, ...options });
7797
+ super({ ...defaults$j, ...options });
7763
7798
  }
7764
7799
  documentation() {
7765
7800
  return {
@@ -7809,7 +7844,7 @@ function parseContent(text) {
7809
7844
  const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7810
7845
  if (match) {
7811
7846
  return {
7812
- delay: parseInt(match[1], 10),
7847
+ delay: Number.parseInt(match[1], 10),
7813
7848
  url: match[2]
7814
7849
  };
7815
7850
  } else {
@@ -7870,7 +7905,7 @@ class MultipleLabeledControls extends Rule {
7870
7905
  }
7871
7906
  }
7872
7907
 
7873
- const defaults$h = {
7908
+ const defaults$i = {
7874
7909
  pattern: "camelcase"
7875
7910
  };
7876
7911
  function exclude(set, ...values) {
@@ -7886,7 +7921,7 @@ class NamePattern extends BasePatternRule {
7886
7921
  super({
7887
7922
  ruleId: "name-pattern",
7888
7923
  attr: "name",
7889
- options: { ...defaults$h, ...options },
7924
+ options: { ...defaults$i, ...options },
7890
7925
  allowedPatterns
7891
7926
  });
7892
7927
  }
@@ -7977,13 +8012,13 @@ class NoAbstractRole extends Rule {
7977
8012
  }
7978
8013
  }
7979
8014
 
7980
- const defaults$g = {
8015
+ const defaults$h = {
7981
8016
  include: null,
7982
8017
  exclude: null
7983
8018
  };
7984
8019
  class NoAutoplay extends Rule {
7985
8020
  constructor(options) {
7986
- super({ ...defaults$g, ...options });
8021
+ super({ ...defaults$h, ...options });
7987
8022
  }
7988
8023
  documentation(context) {
7989
8024
  return {
@@ -8133,13 +8168,12 @@ class NoDupClass extends Rule {
8133
8168
  }
8134
8169
  const classes = new DOMTokenList(event.value, event.valueLocation);
8135
8170
  const unique = /* @__PURE__ */ new Set();
8136
- classes.forEach((cur, index) => {
8137
- if (unique.has(cur)) {
8138
- const location = classes.location(index);
8139
- this.report(event.target, `Class "${cur}" duplicated`, location);
8171
+ for (const { item, location } of classes.iterator()) {
8172
+ if (unique.has(item)) {
8173
+ this.report(event.target, `Class "${item}" duplicated`, location);
8140
8174
  }
8141
- unique.add(cur);
8142
- });
8175
+ unique.add(item);
8176
+ }
8143
8177
  });
8144
8178
  }
8145
8179
  }
@@ -8305,14 +8339,14 @@ class NoImplicitInputType extends Rule {
8305
8339
  }
8306
8340
  }
8307
8341
 
8308
- const defaults$f = {
8342
+ const defaults$g = {
8309
8343
  include: null,
8310
8344
  exclude: null,
8311
8345
  allowedProperties: ["display"]
8312
8346
  };
8313
8347
  class NoInlineStyle extends Rule {
8314
8348
  constructor(options) {
8315
- super({ ...defaults$f, ...options });
8349
+ super({ ...defaults$g, ...options });
8316
8350
  }
8317
8351
  static schema() {
8318
8352
  return {
@@ -8498,7 +8532,7 @@ class NoMultipleMain extends Rule {
8498
8532
  }
8499
8533
  }
8500
8534
 
8501
- const defaults$e = {
8535
+ const defaults$f = {
8502
8536
  relaxed: false
8503
8537
  };
8504
8538
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -8516,7 +8550,7 @@ const replacementTable = {
8516
8550
  class NoRawCharacters extends Rule {
8517
8551
  relaxed;
8518
8552
  constructor(options) {
8519
- super({ ...defaults$e, ...options });
8553
+ super({ ...defaults$f, ...options });
8520
8554
  this.relaxed = this.options.relaxed;
8521
8555
  }
8522
8556
  static schema() {
@@ -8655,7 +8689,14 @@ class NoRedundantFor extends Rule {
8655
8689
  }
8656
8690
  }
8657
8691
 
8692
+ const defaults$e = {
8693
+ include: null,
8694
+ exclude: null
8695
+ };
8658
8696
  class NoRedundantRole extends Rule {
8697
+ constructor(options) {
8698
+ super({ ...defaults$e, ...options });
8699
+ }
8659
8700
  documentation(context) {
8660
8701
  const { role, tagName } = context;
8661
8702
  return {
@@ -8663,6 +8704,36 @@ class NoRedundantRole extends Rule {
8663
8704
  url: "https://html-validate.org/rules/no-redundant-role.html"
8664
8705
  };
8665
8706
  }
8707
+ static schema() {
8708
+ return {
8709
+ exclude: {
8710
+ anyOf: [
8711
+ {
8712
+ items: {
8713
+ type: "string"
8714
+ },
8715
+ type: "array"
8716
+ },
8717
+ {
8718
+ type: "null"
8719
+ }
8720
+ ]
8721
+ },
8722
+ include: {
8723
+ anyOf: [
8724
+ {
8725
+ items: {
8726
+ type: "string"
8727
+ },
8728
+ type: "array"
8729
+ },
8730
+ {
8731
+ type: "null"
8732
+ }
8733
+ ]
8734
+ }
8735
+ };
8736
+ }
8666
8737
  setup() {
8667
8738
  this.on("tag:ready", (event) => {
8668
8739
  const { target } = event;
@@ -8681,6 +8752,9 @@ class NoRedundantRole extends Rule {
8681
8752
  if (role.value !== implicitRole) {
8682
8753
  return;
8683
8754
  }
8755
+ if (this.isKeywordIgnored(role.value)) {
8756
+ return;
8757
+ }
8684
8758
  const context = {
8685
8759
  tagName: target.tagName,
8686
8760
  role: role.value
@@ -8871,10 +8945,10 @@ class NoUnusedDisable extends Rule {
8871
8945
  setup() {
8872
8946
  }
8873
8947
  reportUnused(unused, options, location) {
8874
- const tokens = new DOMTokenList(options.replace(/,/g, " "), location);
8948
+ const tokens = new DOMTokenList(options.replaceAll(",", " "), location);
8875
8949
  for (const ruleId of unused) {
8876
8950
  const index = tokens.indexOf(ruleId);
8877
- const tokenLocation = index >= 0 ? tokens.location(index) : location;
8951
+ const tokenLocation = index !== -1 ? tokens.location(index) : location;
8878
8952
  this.report({
8879
8953
  node: null,
8880
8954
  message: '"{{ ruleId }}" rule is disabled but no error was reported',
@@ -9122,7 +9196,7 @@ class PreferTbody extends Rule {
9122
9196
  continue;
9123
9197
  }
9124
9198
  const tr = table.querySelectorAll("> tr");
9125
- if (tr.length >= 1) {
9199
+ if (tr.length > 0) {
9126
9200
  this.report(tr[0], "Prefer to wrap <tr> elements in <tbody>");
9127
9201
  }
9128
9202
  }
@@ -9192,19 +9266,19 @@ const supportSri = {
9192
9266
  link: "href",
9193
9267
  script: "src"
9194
9268
  };
9195
- const supportedRel = ["stylesheet", "preload", "modulepreload"];
9196
- const supportedPreload = ["style", "script"];
9269
+ const supportedRel = /* @__PURE__ */ new Set(["stylesheet", "preload", "modulepreload"]);
9270
+ const supportedPreload = /* @__PURE__ */ new Set(["style", "script"]);
9197
9271
  function linkSupportsSri(node) {
9198
9272
  const rel = node.getAttribute("rel");
9199
9273
  if (typeof rel?.value !== "string") {
9200
9274
  return false;
9201
9275
  }
9202
- if (!supportedRel.includes(rel.value)) {
9276
+ if (!supportedRel.has(rel.value)) {
9203
9277
  return false;
9204
9278
  }
9205
9279
  if (rel.value === "preload") {
9206
9280
  const as = node.getAttribute("as");
9207
- return typeof as?.value === "string" && supportedPreload.includes(as.value);
9281
+ return typeof as?.value === "string" && supportedPreload.has(as.value);
9208
9282
  }
9209
9283
  return true;
9210
9284
  }
@@ -9321,13 +9395,13 @@ class ScriptElement extends Rule {
9321
9395
  }
9322
9396
  }
9323
9397
 
9324
- const javascript = [
9398
+ const javascript = /* @__PURE__ */ new Set([
9325
9399
  "",
9326
9400
  "application/ecmascript",
9327
9401
  "application/javascript",
9328
9402
  "text/ecmascript",
9329
9403
  "text/javascript"
9330
- ];
9404
+ ]);
9331
9405
  class ScriptType extends Rule {
9332
9406
  documentation() {
9333
9407
  return {
@@ -9358,7 +9432,7 @@ class ScriptType extends Rule {
9358
9432
  }
9359
9433
  isJavascript(mime) {
9360
9434
  const type = mime.replace(/;.*/, "");
9361
- return javascript.includes(type);
9435
+ return javascript.has(type);
9362
9436
  }
9363
9437
  }
9364
9438
 
@@ -9934,7 +10008,7 @@ class UnknownCharReference extends Rule {
9934
10008
  }
9935
10009
 
9936
10010
  const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
9937
- const fieldNames1 = [
10011
+ const fieldNames1 = /* @__PURE__ */ new Set([
9938
10012
  "name",
9939
10013
  "honorific-prefix",
9940
10014
  "given-name",
@@ -9979,8 +10053,8 @@ const fieldNames1 = [
9979
10053
  "sex",
9980
10054
  "url",
9981
10055
  "photo"
9982
- ];
9983
- const fieldNames2 = [
10056
+ ]);
10057
+ const fieldNames2 = /* @__PURE__ */ new Set([
9984
10058
  "tel",
9985
10059
  "tel-country-code",
9986
10060
  "tel-national",
@@ -9991,7 +10065,7 @@ const fieldNames2 = [
9991
10065
  "tel-extension",
9992
10066
  "email",
9993
10067
  "impp"
9994
- ];
10068
+ ]);
9995
10069
  const fieldNameGroup = {
9996
10070
  name: "text",
9997
10071
  "honorific-prefix": "text",
@@ -10056,14 +10130,14 @@ function matchHint(token) {
10056
10130
  return token === "shipping" || token === "billing";
10057
10131
  }
10058
10132
  function matchFieldNames1(token) {
10059
- return fieldNames1.includes(token);
10133
+ return fieldNames1.has(token);
10060
10134
  }
10061
10135
  function matchContact(token) {
10062
10136
  const haystack = ["home", "work", "mobile", "fax", "pager"];
10063
10137
  return haystack.includes(token);
10064
10138
  }
10065
10139
  function matchFieldNames2(token) {
10066
- return fieldNames2.includes(token);
10140
+ return fieldNames2.has(token);
10067
10141
  }
10068
10142
  function matchWebauthn(token) {
10069
10143
  return token === "webauthn";
@@ -10734,11 +10808,13 @@ class H32 extends Rule {
10734
10808
  setup() {
10735
10809
  const formTags = this.getTagsWithProperty("form");
10736
10810
  const formSelector = formTags.join(",");
10811
+ const submitButtonTags = this.getTagsWithProperty("submitButton");
10812
+ const submitButtonSelector = submitButtonTags.join(",");
10737
10813
  this.on("dom:ready", (event) => {
10738
10814
  const { document } = event;
10739
10815
  const forms = document.querySelectorAll(formSelector);
10740
10816
  for (const form of forms) {
10741
- if (hasNestedSubmit(form)) {
10817
+ if (hasNestedSubmit(form, submitButtonSelector)) {
10742
10818
  continue;
10743
10819
  }
10744
10820
  if (hasAssociatedSubmit(document, form)) {
@@ -10750,15 +10826,15 @@ class H32 extends Rule {
10750
10826
  }
10751
10827
  }
10752
10828
  function isSubmit(node) {
10753
- const type = node.getAttribute("type");
10754
- return !type || type.valueMatches(/submit|image/);
10829
+ const meta = node.meta;
10830
+ return Boolean(meta?.submitButton);
10755
10831
  }
10756
10832
  function isAssociated(id, node) {
10757
10833
  const form = node.getAttribute("form");
10758
10834
  return Boolean(form?.valueMatches(id, true));
10759
10835
  }
10760
- function hasNestedSubmit(form) {
10761
- const matches = form.querySelectorAll("button,input").filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10836
+ function hasNestedSubmit(form, submitButtonSelector) {
10837
+ const matches = form.querySelectorAll(submitButtonSelector).filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10762
10838
  return matches.length > 0;
10763
10839
  }
10764
10840
  function hasAssociatedSubmit(document, form) {
@@ -10904,7 +10980,7 @@ function isSimpleTable(table) {
10904
10980
  }
10905
10981
  const shape = getShape(cells);
10906
10982
  const headersPerRow = cells.map((row) => row.reduce((sum, cell) => sum + cell, 0));
10907
- const headersPerColumn = Array(shape.cols).fill(0).map((_, index) => {
10983
+ const headersPerColumn = new Array(shape.cols).fill(0).map((_, index) => {
10908
10984
  return cells.reduce((sum, it) => sum + it[index], 0);
10909
10985
  });
10910
10986
  const [firstRow, ...otherRows] = headersPerRow;
@@ -11307,11 +11383,11 @@ function dumpTree(root) {
11307
11383
  } else {
11308
11384
  lines.push("(root)");
11309
11385
  }
11310
- node.childElements.forEach((child, index) => {
11386
+ for (const [index, child] of node.childElements.entries()) {
11311
11387
  const s = lastSibling ? " " : "\u2502";
11312
11388
  const i = level > 0 ? `${indent}${s} ` : "";
11313
11389
  writeNode(child, level + 1, i, index);
11314
- });
11390
+ }
11315
11391
  }
11316
11392
  writeNode(root, 0, "", 0);
11317
11393
  return lines;
@@ -12426,7 +12502,7 @@ class EventHandler {
12426
12502
  }
12427
12503
 
12428
12504
  const name = "html-validate";
12429
- const version = "10.9.0";
12505
+ const version = "10.11.0";
12430
12506
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12431
12507
 
12432
12508
  function freeze(src) {
@@ -12442,7 +12518,13 @@ function isThenableArray(value) {
12442
12518
  return isThenable(value[0]);
12443
12519
  }
12444
12520
  class Reporter {
12521
+ /**
12522
+ * @internal
12523
+ */
12445
12524
  result;
12525
+ /**
12526
+ * @internal
12527
+ */
12446
12528
  constructor() {
12447
12529
  this.result = {};
12448
12530
  }
@@ -12455,16 +12537,16 @@ class Reporter {
12455
12537
  }
12456
12538
  const valid = reports.every((report) => report.valid);
12457
12539
  const merged = {};
12458
- reports.forEach((report) => {
12459
- report.results.forEach((result) => {
12540
+ for (const report of reports) {
12541
+ for (const result of report.results) {
12460
12542
  const key = result.filePath;
12461
12543
  if (key in merged) {
12462
12544
  merged[key].messages = [...merged[key].messages, ...result.messages];
12463
12545
  } else {
12464
12546
  merged[key] = { ...result };
12465
12547
  }
12466
- });
12467
- });
12548
+ }
12549
+ }
12468
12550
  const results = Object.values(merged).map((result) => {
12469
12551
  result.errorCount = countErrors(result.messages);
12470
12552
  result.warningCount = countWarnings(result.messages);
@@ -12477,8 +12559,11 @@ class Reporter {
12477
12559
  warningCount: sumWarnings(results)
12478
12560
  };
12479
12561
  }
12480
- /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12481
- add(rule, message, severity, node, location, context) {
12562
+ /**
12563
+ * @internal
12564
+ */
12565
+ add(options) {
12566
+ const { rule, message, severity, node, location, context } = options;
12482
12567
  if (!(location.filename in this.result)) {
12483
12568
  this.result[location.filename] = [];
12484
12569
  }
@@ -12503,17 +12588,23 @@ class Reporter {
12503
12588
  }
12504
12589
  this.result[location.filename].push(entry);
12505
12590
  }
12591
+ /**
12592
+ * @internal
12593
+ */
12506
12594
  addManual(filename, message) {
12507
12595
  if (!(filename in this.result)) {
12508
12596
  this.result[filename] = [];
12509
12597
  }
12510
12598
  this.result[filename].push(message);
12511
12599
  }
12600
+ /**
12601
+ * @internal
12602
+ */
12512
12603
  save(sources) {
12513
12604
  const report = {
12514
12605
  valid: this.isValid(),
12515
12606
  results: Object.keys(this.result).map((filePath) => {
12516
- const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
12607
+ const messages = Array.from(this.result[filePath], freeze).toSorted(messageSort);
12517
12608
  const source = (sources ?? []).find((source2) => filePath === source2.filename);
12518
12609
  return {
12519
12610
  filePath,
@@ -12530,6 +12621,9 @@ class Reporter {
12530
12621
  report.warningCount = sumWarnings(report.results);
12531
12622
  return report;
12532
12623
  }
12624
+ /**
12625
+ * @internal
12626
+ */
12533
12627
  isValid() {
12534
12628
  const numErrors = Object.values(this.result).reduce((sum, messages) => {
12535
12629
  return sum + countErrors(messages);
@@ -12592,6 +12686,7 @@ class ParserError extends Error {
12592
12686
  location;
12593
12687
  constructor(location, message) {
12594
12688
  super(message);
12689
+ this.name = "ParserError";
12595
12690
  this.location = location;
12596
12691
  }
12597
12692
  }
@@ -12748,7 +12843,7 @@ class Parser {
12748
12843
  const tokens = Array.from(
12749
12844
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location)
12750
12845
  );
12751
- const endToken = tokens.slice(-1)[0];
12846
+ const endToken = tokens.at(-1);
12752
12847
  const closeOptional = this.closeOptional(startToken);
12753
12848
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
12754
12849
  const node = HtmlElement.fromTokens(
@@ -12866,7 +12961,7 @@ class Parser {
12866
12961
  const endTokens = Array.from(
12867
12962
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location)
12868
12963
  );
12869
- endToken = endTokens.slice(-1)[0];
12964
+ endToken = endTokens.at(-1);
12870
12965
  const selfClosed = endToken.data[0] === "/>";
12871
12966
  if (tagClosed) {
12872
12967
  startToken = last;
@@ -13148,7 +13243,7 @@ class Parser {
13148
13243
  this.event.once("*", cb);
13149
13244
  }
13150
13245
  trigger(event, data) {
13151
- if (typeof data.location === "undefined") {
13246
+ if (data.location === void 0) {
13152
13247
  throw new Error("Triggered event must contain location");
13153
13248
  }
13154
13249
  this.event.trigger(event, data);
@@ -13276,7 +13371,9 @@ class Engine {
13276
13371
  }
13277
13372
  lines.push({ event, data });
13278
13373
  });
13279
- source.forEach((src) => parser.parseHtml(src));
13374
+ for (const src of source) {
13375
+ parser.parseHtml(src);
13376
+ }
13280
13377
  return lines;
13281
13378
  }
13282
13379
  dumpTokens(source) {
@@ -13741,7 +13838,7 @@ const entities = {
13741
13838
  "&": "&amp;"
13742
13839
  };
13743
13840
  function xmlescape(src) {
13744
- return src.toString().replace(/[><'"&]/g, (match) => {
13841
+ return src.toString().replaceAll(/[><'"&]/g, (match) => {
13745
13842
  return entities[match];
13746
13843
  });
13747
13844
  }
@@ -13761,11 +13858,11 @@ function checkstyleFormatter(results) {
13761
13858
  `;
13762
13859
  output += `<checkstyle version="4.3">
13763
13860
  `;
13764
- results.forEach((result) => {
13861
+ for (const result of results) {
13765
13862
  const messages = result.messages;
13766
13863
  output += ` <file name="${xmlescape(result.filePath)}">
13767
13864
  `;
13768
- messages.forEach((message) => {
13865
+ for (const message of messages) {
13769
13866
  const ruleId = xmlescape(`htmlvalidate.rules.${message.ruleId}`);
13770
13867
  output += " ";
13771
13868
  output += [
@@ -13776,9 +13873,9 @@ function checkstyleFormatter(results) {
13776
13873
  `source="${ruleId}" />`
13777
13874
  ].join(" ");
13778
13875
  output += "\n";
13779
- });
13876
+ }
13780
13877
  output += " </file>\n";
13781
- });
13878
+ }
13782
13879
  output += "</checkstyle>\n";
13783
13880
  return output;
13784
13881
  }
@@ -13848,11 +13945,11 @@ function codeFrameColumns(rawLines, loc) {
13848
13945
  if (hasMarker) {
13849
13946
  let markerLine = "";
13850
13947
  if (Array.isArray(hasMarker)) {
13851
- const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
13948
+ const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replaceAll(/[^\t]/g, " ");
13852
13949
  const numberOfMarkers = hasMarker[1] || 1;
13853
13950
  markerLine = [
13854
13951
  "\n ",
13855
- gutter.replace(/\d/g, " "),
13952
+ gutter.replaceAll(/\d/g, " "),
13856
13953
  " ",
13857
13954
  markerSpacing,
13858
13955
  "^".repeat(numberOfMarkers)
@@ -13990,10 +14087,10 @@ function stylish(results) {
13990
14087
  function textFormatter(results) {
13991
14088
  let output = "";
13992
14089
  let total = 0;
13993
- results.forEach((result) => {
14090
+ for (const result of results) {
13994
14091
  const messages = result.messages;
13995
14092
  if (messages.length === 0) {
13996
- return;
14093
+ continue;
13997
14094
  }
13998
14095
  total += messages.length;
13999
14096
  output += messages.map((message) => {
@@ -14009,7 +14106,7 @@ function textFormatter(results) {
14009
14106
  return `${location}: ${messageType} [${message.ruleId}] ${message.message}
14010
14107
  `;
14011
14108
  }).join("");
14012
- });
14109
+ }
14013
14110
  return total > 0 ? output : "";
14014
14111
  }
14015
14112
  const formatter = textFormatter;
@@ -14841,7 +14938,7 @@ var ignoreExports = /*@__PURE__*/ requireIgnore();
14841
14938
  var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14842
14939
 
14843
14940
  const engines = {
14844
- node: "^20.19.0 || >= 22.12.0"
14941
+ node: "^20.19.0 || >= 22.16.0"
14845
14942
  };
14846
14943
 
14847
14944
  var workerPath = "./jest-worker.js";