html-validate 10.9.0 → 10.10.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 +179 -120
  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 +179 -120
  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
@@ -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
  });
@@ -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) {
@@ -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
 
@@ -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
  }
@@ -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;
@@ -5907,7 +5937,7 @@ class Deprecated extends Rule {
5907
5937
  text.push(context.documentation);
5908
5938
  }
5909
5939
  const doc = {
5910
- description: text.map((cur) => cur.replace(/\$tagname/g, context.tagName)).join("\n\n"),
5940
+ description: text.map((cur) => cur.replaceAll("$tagname", context.tagName)).join("\n\n"),
5911
5941
  url: "https://html-validate.org/rules/deprecated.html"
5912
5942
  };
5913
5943
  return doc;
@@ -6226,7 +6256,7 @@ class ElementCase extends Rule {
6226
6256
  });
6227
6257
  }
6228
6258
  validateCase(target, targetLocation) {
6229
- const letters = target.tagName.replace(/[^a-z]+/gi, "");
6259
+ const letters = target.tagName.replaceAll(/[^a-z]+/gi, "");
6230
6260
  if (!this.style.match(letters)) {
6231
6261
  const location = sliceLocation(targetLocation, 1);
6232
6262
  this.report(target, `Element "${target.tagName}" should be ${this.style.name}`, location);
@@ -6830,8 +6860,8 @@ function haveName(name) {
6830
6860
  return typeof name === "string" && name !== "";
6831
6861
  }
6832
6862
  function allowSharedName(node, shared) {
6833
- const type = node.getAttribute("type");
6834
- return Boolean(type?.valueMatches(shared, false));
6863
+ const type = getControlType(node);
6864
+ return shared.includes(type);
6835
6865
  }
6836
6866
  function isInputHidden(element) {
6837
6867
  return element.is("input") && element.getAttributeValue("type") === "hidden";
@@ -6839,6 +6869,13 @@ function isInputHidden(element) {
6839
6869
  function isInputCheckbox(element) {
6840
6870
  return element.is("input") && element.getAttributeValue("type") === "checkbox";
6841
6871
  }
6872
+ function getControlType(element) {
6873
+ const type = element.getAttributeValue("type") ?? "";
6874
+ if (element.is("button") && type === "") {
6875
+ return "submit";
6876
+ }
6877
+ return type;
6878
+ }
6842
6879
  function isCheckboxWithDefault(control, previous, options) {
6843
6880
  const { allowCheckboxDefault } = options;
6844
6881
  if (!allowCheckboxDefault) {
@@ -6976,7 +7013,7 @@ class FormDupName extends Rule {
6976
7013
  validateSharedName(control, group, attr, name) {
6977
7014
  const uniqueElements = this.getUniqueElements(group);
6978
7015
  const sharedElements = this.getSharedElements(group);
6979
- const type = control.getAttributeValue("type") ?? "";
7016
+ const type = getControlType(control);
6980
7017
  if (uniqueElements.has(name) || sharedElements.has(name) && sharedElements.get(name) !== type) {
6981
7018
  const context = {
6982
7019
  name,
@@ -7038,7 +7075,7 @@ function isRelevant$5(event) {
7038
7075
  function extractLevel(node) {
7039
7076
  const match = /^[hH](\d)$/.exec(node.tagName);
7040
7077
  if (match) {
7041
- return parseInt(match[1], 10);
7078
+ return Number.parseInt(match[1], 10);
7042
7079
  } else {
7043
7080
  return null;
7044
7081
  }
@@ -7051,7 +7088,7 @@ function parseMaxInitial(value) {
7051
7088
  if (!match) {
7052
7089
  return 1;
7053
7090
  }
7054
- return parseInt(match[1], 10);
7091
+ return Number.parseInt(match[1], 10);
7055
7092
  }
7056
7093
  class HeadingLevel extends Rule {
7057
7094
  minInitialRank;
@@ -7200,10 +7237,10 @@ class HeadingLevel extends Rule {
7200
7237
  this.stack.pop();
7201
7238
  }
7202
7239
  getPrevRoot() {
7203
- return this.stack[this.stack.length - 2];
7240
+ return this.stack.at(-2);
7204
7241
  }
7205
7242
  getCurrentRoot() {
7206
- return this.stack[this.stack.length - 1];
7243
+ return this.stack.at(-1);
7207
7244
  }
7208
7245
  isSectioningRoot(node) {
7209
7246
  const context = {
@@ -7800,7 +7837,7 @@ function parseContent(text) {
7800
7837
  const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7801
7838
  if (match) {
7802
7839
  return {
7803
- delay: parseInt(match[1], 10),
7840
+ delay: Number.parseInt(match[1], 10),
7804
7841
  url: match[2]
7805
7842
  };
7806
7843
  } else {
@@ -8124,13 +8161,12 @@ class NoDupClass extends Rule {
8124
8161
  }
8125
8162
  const classes = new DOMTokenList(event.value, event.valueLocation);
8126
8163
  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);
8164
+ for (const { item, location } of classes.iterator()) {
8165
+ if (unique.has(item)) {
8166
+ this.report(event.target, `Class "${item}" duplicated`, location);
8131
8167
  }
8132
- unique.add(cur);
8133
- });
8168
+ unique.add(item);
8169
+ }
8134
8170
  });
8135
8171
  }
8136
8172
  }
@@ -8862,7 +8898,7 @@ class NoUnusedDisable extends Rule {
8862
8898
  setup() {
8863
8899
  }
8864
8900
  reportUnused(unused, options, location) {
8865
- const tokens = new DOMTokenList(options.replace(/,/g, " "), location);
8901
+ const tokens = new DOMTokenList(options.replaceAll(",", " "), location);
8866
8902
  for (const ruleId of unused) {
8867
8903
  const index = tokens.indexOf(ruleId);
8868
8904
  const tokenLocation = index >= 0 ? tokens.location(index) : location;
@@ -9183,19 +9219,19 @@ const supportSri = {
9183
9219
  link: "href",
9184
9220
  script: "src"
9185
9221
  };
9186
- const supportedRel = ["stylesheet", "preload", "modulepreload"];
9187
- const supportedPreload = ["style", "script"];
9222
+ const supportedRel = /* @__PURE__ */ new Set(["stylesheet", "preload", "modulepreload"]);
9223
+ const supportedPreload = /* @__PURE__ */ new Set(["style", "script"]);
9188
9224
  function linkSupportsSri(node) {
9189
9225
  const rel = node.getAttribute("rel");
9190
9226
  if (typeof rel?.value !== "string") {
9191
9227
  return false;
9192
9228
  }
9193
- if (!supportedRel.includes(rel.value)) {
9229
+ if (!supportedRel.has(rel.value)) {
9194
9230
  return false;
9195
9231
  }
9196
9232
  if (rel.value === "preload") {
9197
9233
  const as = node.getAttribute("as");
9198
- return typeof as?.value === "string" && supportedPreload.includes(as.value);
9234
+ return typeof as?.value === "string" && supportedPreload.has(as.value);
9199
9235
  }
9200
9236
  return true;
9201
9237
  }
@@ -9312,13 +9348,13 @@ class ScriptElement extends Rule {
9312
9348
  }
9313
9349
  }
9314
9350
 
9315
- const javascript = [
9351
+ const javascript = /* @__PURE__ */ new Set([
9316
9352
  "",
9317
9353
  "application/ecmascript",
9318
9354
  "application/javascript",
9319
9355
  "text/ecmascript",
9320
9356
  "text/javascript"
9321
- ];
9357
+ ]);
9322
9358
  class ScriptType extends Rule {
9323
9359
  documentation() {
9324
9360
  return {
@@ -9349,7 +9385,7 @@ class ScriptType extends Rule {
9349
9385
  }
9350
9386
  isJavascript(mime) {
9351
9387
  const type = mime.replace(/;.*/, "");
9352
- return javascript.includes(type);
9388
+ return javascript.has(type);
9353
9389
  }
9354
9390
  }
9355
9391
 
@@ -9925,7 +9961,7 @@ class UnknownCharReference extends Rule {
9925
9961
  }
9926
9962
 
9927
9963
  const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
9928
- const fieldNames1 = [
9964
+ const fieldNames1 = /* @__PURE__ */ new Set([
9929
9965
  "name",
9930
9966
  "honorific-prefix",
9931
9967
  "given-name",
@@ -9970,8 +10006,8 @@ const fieldNames1 = [
9970
10006
  "sex",
9971
10007
  "url",
9972
10008
  "photo"
9973
- ];
9974
- const fieldNames2 = [
10009
+ ]);
10010
+ const fieldNames2 = /* @__PURE__ */ new Set([
9975
10011
  "tel",
9976
10012
  "tel-country-code",
9977
10013
  "tel-national",
@@ -9982,7 +10018,7 @@ const fieldNames2 = [
9982
10018
  "tel-extension",
9983
10019
  "email",
9984
10020
  "impp"
9985
- ];
10021
+ ]);
9986
10022
  const fieldNameGroup = {
9987
10023
  name: "text",
9988
10024
  "honorific-prefix": "text",
@@ -10047,14 +10083,14 @@ function matchHint(token) {
10047
10083
  return token === "shipping" || token === "billing";
10048
10084
  }
10049
10085
  function matchFieldNames1(token) {
10050
- return fieldNames1.includes(token);
10086
+ return fieldNames1.has(token);
10051
10087
  }
10052
10088
  function matchContact(token) {
10053
10089
  const haystack = ["home", "work", "mobile", "fax", "pager"];
10054
10090
  return haystack.includes(token);
10055
10091
  }
10056
10092
  function matchFieldNames2(token) {
10057
- return fieldNames2.includes(token);
10093
+ return fieldNames2.has(token);
10058
10094
  }
10059
10095
  function matchWebauthn(token) {
10060
10096
  return token === "webauthn";
@@ -10725,11 +10761,13 @@ class H32 extends Rule {
10725
10761
  setup() {
10726
10762
  const formTags = this.getTagsWithProperty("form");
10727
10763
  const formSelector = formTags.join(",");
10764
+ const submitButtonTags = this.getTagsWithProperty("submitButton");
10765
+ const submitButtonSelector = submitButtonTags.join(",");
10728
10766
  this.on("dom:ready", (event) => {
10729
10767
  const { document } = event;
10730
10768
  const forms = document.querySelectorAll(formSelector);
10731
10769
  for (const form of forms) {
10732
- if (hasNestedSubmit(form)) {
10770
+ if (hasNestedSubmit(form, submitButtonSelector)) {
10733
10771
  continue;
10734
10772
  }
10735
10773
  if (hasAssociatedSubmit(document, form)) {
@@ -10741,15 +10779,15 @@ class H32 extends Rule {
10741
10779
  }
10742
10780
  }
10743
10781
  function isSubmit(node) {
10744
- const type = node.getAttribute("type");
10745
- return !type || type.valueMatches(/submit|image/);
10782
+ const meta = node.meta;
10783
+ return Boolean(meta?.submitButton);
10746
10784
  }
10747
10785
  function isAssociated(id, node) {
10748
10786
  const form = node.getAttribute("form");
10749
10787
  return Boolean(form?.valueMatches(id, true));
10750
10788
  }
10751
- function hasNestedSubmit(form) {
10752
- const matches = form.querySelectorAll("button,input").filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10789
+ function hasNestedSubmit(form, submitButtonSelector) {
10790
+ const matches = form.querySelectorAll(submitButtonSelector).filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10753
10791
  return matches.length > 0;
10754
10792
  }
10755
10793
  function hasAssociatedSubmit(document, form) {
@@ -11298,11 +11336,11 @@ function dumpTree(root) {
11298
11336
  } else {
11299
11337
  lines.push("(root)");
11300
11338
  }
11301
- node.childElements.forEach((child, index) => {
11339
+ for (const [index, child] of node.childElements.entries()) {
11302
11340
  const s = lastSibling ? " " : "\u2502";
11303
11341
  const i = level > 0 ? `${indent}${s} ` : "";
11304
11342
  writeNode(child, level + 1, i, index);
11305
- });
11343
+ }
11306
11344
  }
11307
11345
  writeNode(root, 0, "", 0);
11308
11346
  return lines;
@@ -12417,7 +12455,7 @@ class EventHandler {
12417
12455
  }
12418
12456
 
12419
12457
  const name = "html-validate";
12420
- const version = "10.9.0";
12458
+ const version = "10.10.0";
12421
12459
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12422
12460
 
12423
12461
  function freeze(src) {
@@ -12433,7 +12471,13 @@ function isThenableArray(value) {
12433
12471
  return isThenable(value[0]);
12434
12472
  }
12435
12473
  class Reporter {
12474
+ /**
12475
+ * @internal
12476
+ */
12436
12477
  result;
12478
+ /**
12479
+ * @internal
12480
+ */
12437
12481
  constructor() {
12438
12482
  this.result = {};
12439
12483
  }
@@ -12446,16 +12490,16 @@ class Reporter {
12446
12490
  }
12447
12491
  const valid = reports.every((report) => report.valid);
12448
12492
  const merged = {};
12449
- reports.forEach((report) => {
12450
- report.results.forEach((result) => {
12493
+ for (const report of reports) {
12494
+ for (const result of report.results) {
12451
12495
  const key = result.filePath;
12452
12496
  if (key in merged) {
12453
12497
  merged[key].messages = [...merged[key].messages, ...result.messages];
12454
12498
  } else {
12455
12499
  merged[key] = { ...result };
12456
12500
  }
12457
- });
12458
- });
12501
+ }
12502
+ }
12459
12503
  const results = Object.values(merged).map((result) => {
12460
12504
  result.errorCount = countErrors(result.messages);
12461
12505
  result.warningCount = countWarnings(result.messages);
@@ -12468,8 +12512,11 @@ class Reporter {
12468
12512
  warningCount: sumWarnings(results)
12469
12513
  };
12470
12514
  }
12471
- /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12472
- add(rule, message, severity, node, location, context) {
12515
+ /**
12516
+ * @internal
12517
+ */
12518
+ add(options) {
12519
+ const { rule, message, severity, node, location, context } = options;
12473
12520
  if (!(location.filename in this.result)) {
12474
12521
  this.result[location.filename] = [];
12475
12522
  }
@@ -12494,17 +12541,23 @@ class Reporter {
12494
12541
  }
12495
12542
  this.result[location.filename].push(entry);
12496
12543
  }
12544
+ /**
12545
+ * @internal
12546
+ */
12497
12547
  addManual(filename, message) {
12498
12548
  if (!(filename in this.result)) {
12499
12549
  this.result[filename] = [];
12500
12550
  }
12501
12551
  this.result[filename].push(message);
12502
12552
  }
12553
+ /**
12554
+ * @internal
12555
+ */
12503
12556
  save(sources) {
12504
12557
  const report = {
12505
12558
  valid: this.isValid(),
12506
12559
  results: Object.keys(this.result).map((filePath) => {
12507
- const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
12560
+ const messages = Array.from(this.result[filePath], freeze).toSorted(messageSort);
12508
12561
  const source = (sources ?? []).find((source2) => filePath === source2.filename);
12509
12562
  return {
12510
12563
  filePath,
@@ -12521,6 +12574,9 @@ class Reporter {
12521
12574
  report.warningCount = sumWarnings(report.results);
12522
12575
  return report;
12523
12576
  }
12577
+ /**
12578
+ * @internal
12579
+ */
12524
12580
  isValid() {
12525
12581
  const numErrors = Object.values(this.result).reduce((sum, messages) => {
12526
12582
  return sum + countErrors(messages);
@@ -12583,6 +12639,7 @@ class ParserError extends Error {
12583
12639
  location;
12584
12640
  constructor(location, message) {
12585
12641
  super(message);
12642
+ this.name = "ParserError";
12586
12643
  this.location = location;
12587
12644
  }
12588
12645
  }
@@ -12739,7 +12796,7 @@ class Parser {
12739
12796
  const tokens = Array.from(
12740
12797
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location)
12741
12798
  );
12742
- const endToken = tokens.slice(-1)[0];
12799
+ const endToken = tokens.at(-1);
12743
12800
  const closeOptional = this.closeOptional(startToken);
12744
12801
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
12745
12802
  const node = HtmlElement.fromTokens(
@@ -12857,7 +12914,7 @@ class Parser {
12857
12914
  const endTokens = Array.from(
12858
12915
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location)
12859
12916
  );
12860
- endToken = endTokens.slice(-1)[0];
12917
+ endToken = endTokens.at(-1);
12861
12918
  const selfClosed = endToken.data[0] === "/>";
12862
12919
  if (tagClosed) {
12863
12920
  startToken = last;
@@ -13139,7 +13196,7 @@ class Parser {
13139
13196
  this.event.once("*", cb);
13140
13197
  }
13141
13198
  trigger(event, data) {
13142
- if (typeof data.location === "undefined") {
13199
+ if (data.location === void 0) {
13143
13200
  throw new Error("Triggered event must contain location");
13144
13201
  }
13145
13202
  this.event.trigger(event, data);
@@ -13267,7 +13324,9 @@ class Engine {
13267
13324
  }
13268
13325
  lines.push({ event, data });
13269
13326
  });
13270
- source.forEach((src) => parser.parseHtml(src));
13327
+ for (const src of source) {
13328
+ parser.parseHtml(src);
13329
+ }
13271
13330
  return lines;
13272
13331
  }
13273
13332
  dumpTokens(source) {
@@ -13732,7 +13791,7 @@ const entities = {
13732
13791
  "&": "&amp;"
13733
13792
  };
13734
13793
  function xmlescape(src) {
13735
- return src.toString().replace(/[><'"&]/g, (match) => {
13794
+ return src.toString().replaceAll(/[><'"&]/g, (match) => {
13736
13795
  return entities[match];
13737
13796
  });
13738
13797
  }
@@ -13752,11 +13811,11 @@ function checkstyleFormatter(results) {
13752
13811
  `;
13753
13812
  output += `<checkstyle version="4.3">
13754
13813
  `;
13755
- results.forEach((result) => {
13814
+ for (const result of results) {
13756
13815
  const messages = result.messages;
13757
13816
  output += ` <file name="${xmlescape(result.filePath)}">
13758
13817
  `;
13759
- messages.forEach((message) => {
13818
+ for (const message of messages) {
13760
13819
  const ruleId = xmlescape(`htmlvalidate.rules.${message.ruleId}`);
13761
13820
  output += " ";
13762
13821
  output += [
@@ -13767,9 +13826,9 @@ function checkstyleFormatter(results) {
13767
13826
  `source="${ruleId}" />`
13768
13827
  ].join(" ");
13769
13828
  output += "\n";
13770
- });
13829
+ }
13771
13830
  output += " </file>\n";
13772
- });
13831
+ }
13773
13832
  output += "</checkstyle>\n";
13774
13833
  return output;
13775
13834
  }
@@ -13839,11 +13898,11 @@ function codeFrameColumns(rawLines, loc) {
13839
13898
  if (hasMarker) {
13840
13899
  let markerLine = "";
13841
13900
  if (Array.isArray(hasMarker)) {
13842
- const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
13901
+ const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replaceAll(/[^\t]/g, " ");
13843
13902
  const numberOfMarkers = hasMarker[1] || 1;
13844
13903
  markerLine = [
13845
13904
  "\n ",
13846
- gutter.replace(/\d/g, " "),
13905
+ gutter.replaceAll(/\d/g, " "),
13847
13906
  " ",
13848
13907
  markerSpacing,
13849
13908
  "^".repeat(numberOfMarkers)
@@ -13981,10 +14040,10 @@ function stylish(results) {
13981
14040
  function textFormatter(results) {
13982
14041
  let output = "";
13983
14042
  let total = 0;
13984
- results.forEach((result) => {
14043
+ for (const result of results) {
13985
14044
  const messages = result.messages;
13986
14045
  if (messages.length === 0) {
13987
- return;
14046
+ continue;
13988
14047
  }
13989
14048
  total += messages.length;
13990
14049
  output += messages.map((message) => {
@@ -14000,7 +14059,7 @@ function textFormatter(results) {
14000
14059
  return `${location}: ${messageType} [${message.ruleId}] ${message.message}
14001
14060
  `;
14002
14061
  }).join("");
14003
- });
14062
+ }
14004
14063
  return total > 0 ? output : "";
14005
14064
  }
14006
14065
  const formatter = textFormatter;
@@ -14832,7 +14891,7 @@ var ignoreExports = /*@__PURE__*/ requireIgnore();
14832
14891
  var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14833
14892
 
14834
14893
  const engines = {
14835
- node: "^20.19.0 || >= 22.12.0"
14894
+ node: "^20.19.0 || >= 22.16.0"
14836
14895
  };
14837
14896
 
14838
14897
  var workerPath = "./jest-worker.js";