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/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
@@ -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
  });
@@ -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) {
@@ -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
 
@@ -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
  }
@@ -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;
@@ -5916,7 +5946,7 @@ class Deprecated extends Rule {
5916
5946
  text.push(context.documentation);
5917
5947
  }
5918
5948
  const doc = {
5919
- description: text.map((cur) => cur.replace(/\$tagname/g, context.tagName)).join("\n\n"),
5949
+ description: text.map((cur) => cur.replaceAll("$tagname", context.tagName)).join("\n\n"),
5920
5950
  url: "https://html-validate.org/rules/deprecated.html"
5921
5951
  };
5922
5952
  return doc;
@@ -6235,7 +6265,7 @@ class ElementCase extends Rule {
6235
6265
  });
6236
6266
  }
6237
6267
  validateCase(target, targetLocation) {
6238
- const letters = target.tagName.replace(/[^a-z]+/gi, "");
6268
+ const letters = target.tagName.replaceAll(/[^a-z]+/gi, "");
6239
6269
  if (!this.style.match(letters)) {
6240
6270
  const location = sliceLocation(targetLocation, 1);
6241
6271
  this.report(target, `Element "${target.tagName}" should be ${this.style.name}`, location);
@@ -6839,8 +6869,8 @@ function haveName(name) {
6839
6869
  return typeof name === "string" && name !== "";
6840
6870
  }
6841
6871
  function allowSharedName(node, shared) {
6842
- const type = node.getAttribute("type");
6843
- return Boolean(type?.valueMatches(shared, false));
6872
+ const type = getControlType(node);
6873
+ return shared.includes(type);
6844
6874
  }
6845
6875
  function isInputHidden(element) {
6846
6876
  return element.is("input") && element.getAttributeValue("type") === "hidden";
@@ -6848,6 +6878,13 @@ function isInputHidden(element) {
6848
6878
  function isInputCheckbox(element) {
6849
6879
  return element.is("input") && element.getAttributeValue("type") === "checkbox";
6850
6880
  }
6881
+ function getControlType(element) {
6882
+ const type = element.getAttributeValue("type") ?? "";
6883
+ if (element.is("button") && type === "") {
6884
+ return "submit";
6885
+ }
6886
+ return type;
6887
+ }
6851
6888
  function isCheckboxWithDefault(control, previous, options) {
6852
6889
  const { allowCheckboxDefault } = options;
6853
6890
  if (!allowCheckboxDefault) {
@@ -6985,7 +7022,7 @@ class FormDupName extends Rule {
6985
7022
  validateSharedName(control, group, attr, name) {
6986
7023
  const uniqueElements = this.getUniqueElements(group);
6987
7024
  const sharedElements = this.getSharedElements(group);
6988
- const type = control.getAttributeValue("type") ?? "";
7025
+ const type = getControlType(control);
6989
7026
  if (uniqueElements.has(name) || sharedElements.has(name) && sharedElements.get(name) !== type) {
6990
7027
  const context = {
6991
7028
  name,
@@ -7047,7 +7084,7 @@ function isRelevant$5(event) {
7047
7084
  function extractLevel(node) {
7048
7085
  const match = /^[hH](\d)$/.exec(node.tagName);
7049
7086
  if (match) {
7050
- return parseInt(match[1], 10);
7087
+ return Number.parseInt(match[1], 10);
7051
7088
  } else {
7052
7089
  return null;
7053
7090
  }
@@ -7060,7 +7097,7 @@ function parseMaxInitial(value) {
7060
7097
  if (!match) {
7061
7098
  return 1;
7062
7099
  }
7063
- return parseInt(match[1], 10);
7100
+ return Number.parseInt(match[1], 10);
7064
7101
  }
7065
7102
  class HeadingLevel extends Rule {
7066
7103
  minInitialRank;
@@ -7209,10 +7246,10 @@ class HeadingLevel extends Rule {
7209
7246
  this.stack.pop();
7210
7247
  }
7211
7248
  getPrevRoot() {
7212
- return this.stack[this.stack.length - 2];
7249
+ return this.stack.at(-2);
7213
7250
  }
7214
7251
  getCurrentRoot() {
7215
- return this.stack[this.stack.length - 1];
7252
+ return this.stack.at(-1);
7216
7253
  }
7217
7254
  isSectioningRoot(node) {
7218
7255
  const context = {
@@ -7809,7 +7846,7 @@ function parseContent(text) {
7809
7846
  const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7810
7847
  if (match) {
7811
7848
  return {
7812
- delay: parseInt(match[1], 10),
7849
+ delay: Number.parseInt(match[1], 10),
7813
7850
  url: match[2]
7814
7851
  };
7815
7852
  } else {
@@ -8133,13 +8170,12 @@ class NoDupClass extends Rule {
8133
8170
  }
8134
8171
  const classes = new DOMTokenList(event.value, event.valueLocation);
8135
8172
  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);
8173
+ for (const { item, location } of classes.iterator()) {
8174
+ if (unique.has(item)) {
8175
+ this.report(event.target, `Class "${item}" duplicated`, location);
8140
8176
  }
8141
- unique.add(cur);
8142
- });
8177
+ unique.add(item);
8178
+ }
8143
8179
  });
8144
8180
  }
8145
8181
  }
@@ -8871,7 +8907,7 @@ class NoUnusedDisable extends Rule {
8871
8907
  setup() {
8872
8908
  }
8873
8909
  reportUnused(unused, options, location) {
8874
- const tokens = new DOMTokenList(options.replace(/,/g, " "), location);
8910
+ const tokens = new DOMTokenList(options.replaceAll(",", " "), location);
8875
8911
  for (const ruleId of unused) {
8876
8912
  const index = tokens.indexOf(ruleId);
8877
8913
  const tokenLocation = index >= 0 ? tokens.location(index) : location;
@@ -9192,19 +9228,19 @@ const supportSri = {
9192
9228
  link: "href",
9193
9229
  script: "src"
9194
9230
  };
9195
- const supportedRel = ["stylesheet", "preload", "modulepreload"];
9196
- const supportedPreload = ["style", "script"];
9231
+ const supportedRel = /* @__PURE__ */ new Set(["stylesheet", "preload", "modulepreload"]);
9232
+ const supportedPreload = /* @__PURE__ */ new Set(["style", "script"]);
9197
9233
  function linkSupportsSri(node) {
9198
9234
  const rel = node.getAttribute("rel");
9199
9235
  if (typeof rel?.value !== "string") {
9200
9236
  return false;
9201
9237
  }
9202
- if (!supportedRel.includes(rel.value)) {
9238
+ if (!supportedRel.has(rel.value)) {
9203
9239
  return false;
9204
9240
  }
9205
9241
  if (rel.value === "preload") {
9206
9242
  const as = node.getAttribute("as");
9207
- return typeof as?.value === "string" && supportedPreload.includes(as.value);
9243
+ return typeof as?.value === "string" && supportedPreload.has(as.value);
9208
9244
  }
9209
9245
  return true;
9210
9246
  }
@@ -9321,13 +9357,13 @@ class ScriptElement extends Rule {
9321
9357
  }
9322
9358
  }
9323
9359
 
9324
- const javascript = [
9360
+ const javascript = /* @__PURE__ */ new Set([
9325
9361
  "",
9326
9362
  "application/ecmascript",
9327
9363
  "application/javascript",
9328
9364
  "text/ecmascript",
9329
9365
  "text/javascript"
9330
- ];
9366
+ ]);
9331
9367
  class ScriptType extends Rule {
9332
9368
  documentation() {
9333
9369
  return {
@@ -9358,7 +9394,7 @@ class ScriptType extends Rule {
9358
9394
  }
9359
9395
  isJavascript(mime) {
9360
9396
  const type = mime.replace(/;.*/, "");
9361
- return javascript.includes(type);
9397
+ return javascript.has(type);
9362
9398
  }
9363
9399
  }
9364
9400
 
@@ -9934,7 +9970,7 @@ class UnknownCharReference extends Rule {
9934
9970
  }
9935
9971
 
9936
9972
  const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
9937
- const fieldNames1 = [
9973
+ const fieldNames1 = /* @__PURE__ */ new Set([
9938
9974
  "name",
9939
9975
  "honorific-prefix",
9940
9976
  "given-name",
@@ -9979,8 +10015,8 @@ const fieldNames1 = [
9979
10015
  "sex",
9980
10016
  "url",
9981
10017
  "photo"
9982
- ];
9983
- const fieldNames2 = [
10018
+ ]);
10019
+ const fieldNames2 = /* @__PURE__ */ new Set([
9984
10020
  "tel",
9985
10021
  "tel-country-code",
9986
10022
  "tel-national",
@@ -9991,7 +10027,7 @@ const fieldNames2 = [
9991
10027
  "tel-extension",
9992
10028
  "email",
9993
10029
  "impp"
9994
- ];
10030
+ ]);
9995
10031
  const fieldNameGroup = {
9996
10032
  name: "text",
9997
10033
  "honorific-prefix": "text",
@@ -10056,14 +10092,14 @@ function matchHint(token) {
10056
10092
  return token === "shipping" || token === "billing";
10057
10093
  }
10058
10094
  function matchFieldNames1(token) {
10059
- return fieldNames1.includes(token);
10095
+ return fieldNames1.has(token);
10060
10096
  }
10061
10097
  function matchContact(token) {
10062
10098
  const haystack = ["home", "work", "mobile", "fax", "pager"];
10063
10099
  return haystack.includes(token);
10064
10100
  }
10065
10101
  function matchFieldNames2(token) {
10066
- return fieldNames2.includes(token);
10102
+ return fieldNames2.has(token);
10067
10103
  }
10068
10104
  function matchWebauthn(token) {
10069
10105
  return token === "webauthn";
@@ -10734,11 +10770,13 @@ class H32 extends Rule {
10734
10770
  setup() {
10735
10771
  const formTags = this.getTagsWithProperty("form");
10736
10772
  const formSelector = formTags.join(",");
10773
+ const submitButtonTags = this.getTagsWithProperty("submitButton");
10774
+ const submitButtonSelector = submitButtonTags.join(",");
10737
10775
  this.on("dom:ready", (event) => {
10738
10776
  const { document } = event;
10739
10777
  const forms = document.querySelectorAll(formSelector);
10740
10778
  for (const form of forms) {
10741
- if (hasNestedSubmit(form)) {
10779
+ if (hasNestedSubmit(form, submitButtonSelector)) {
10742
10780
  continue;
10743
10781
  }
10744
10782
  if (hasAssociatedSubmit(document, form)) {
@@ -10750,15 +10788,15 @@ class H32 extends Rule {
10750
10788
  }
10751
10789
  }
10752
10790
  function isSubmit(node) {
10753
- const type = node.getAttribute("type");
10754
- return !type || type.valueMatches(/submit|image/);
10791
+ const meta = node.meta;
10792
+ return Boolean(meta?.submitButton);
10755
10793
  }
10756
10794
  function isAssociated(id, node) {
10757
10795
  const form = node.getAttribute("form");
10758
10796
  return Boolean(form?.valueMatches(id, true));
10759
10797
  }
10760
- function hasNestedSubmit(form) {
10761
- const matches = form.querySelectorAll("button,input").filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10798
+ function hasNestedSubmit(form, submitButtonSelector) {
10799
+ const matches = form.querySelectorAll(submitButtonSelector).filter(isSubmit).filter((node) => !node.hasAttribute("form"));
10762
10800
  return matches.length > 0;
10763
10801
  }
10764
10802
  function hasAssociatedSubmit(document, form) {
@@ -11307,11 +11345,11 @@ function dumpTree(root) {
11307
11345
  } else {
11308
11346
  lines.push("(root)");
11309
11347
  }
11310
- node.childElements.forEach((child, index) => {
11348
+ for (const [index, child] of node.childElements.entries()) {
11311
11349
  const s = lastSibling ? " " : "\u2502";
11312
11350
  const i = level > 0 ? `${indent}${s} ` : "";
11313
11351
  writeNode(child, level + 1, i, index);
11314
- });
11352
+ }
11315
11353
  }
11316
11354
  writeNode(root, 0, "", 0);
11317
11355
  return lines;
@@ -12426,7 +12464,7 @@ class EventHandler {
12426
12464
  }
12427
12465
 
12428
12466
  const name = "html-validate";
12429
- const version = "10.9.0";
12467
+ const version = "10.10.0";
12430
12468
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12431
12469
 
12432
12470
  function freeze(src) {
@@ -12442,7 +12480,13 @@ function isThenableArray(value) {
12442
12480
  return isThenable(value[0]);
12443
12481
  }
12444
12482
  class Reporter {
12483
+ /**
12484
+ * @internal
12485
+ */
12445
12486
  result;
12487
+ /**
12488
+ * @internal
12489
+ */
12446
12490
  constructor() {
12447
12491
  this.result = {};
12448
12492
  }
@@ -12455,16 +12499,16 @@ class Reporter {
12455
12499
  }
12456
12500
  const valid = reports.every((report) => report.valid);
12457
12501
  const merged = {};
12458
- reports.forEach((report) => {
12459
- report.results.forEach((result) => {
12502
+ for (const report of reports) {
12503
+ for (const result of report.results) {
12460
12504
  const key = result.filePath;
12461
12505
  if (key in merged) {
12462
12506
  merged[key].messages = [...merged[key].messages, ...result.messages];
12463
12507
  } else {
12464
12508
  merged[key] = { ...result };
12465
12509
  }
12466
- });
12467
- });
12510
+ }
12511
+ }
12468
12512
  const results = Object.values(merged).map((result) => {
12469
12513
  result.errorCount = countErrors(result.messages);
12470
12514
  result.warningCount = countWarnings(result.messages);
@@ -12477,8 +12521,11 @@ class Reporter {
12477
12521
  warningCount: sumWarnings(results)
12478
12522
  };
12479
12523
  }
12480
- /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12481
- add(rule, message, severity, node, location, context) {
12524
+ /**
12525
+ * @internal
12526
+ */
12527
+ add(options) {
12528
+ const { rule, message, severity, node, location, context } = options;
12482
12529
  if (!(location.filename in this.result)) {
12483
12530
  this.result[location.filename] = [];
12484
12531
  }
@@ -12503,17 +12550,23 @@ class Reporter {
12503
12550
  }
12504
12551
  this.result[location.filename].push(entry);
12505
12552
  }
12553
+ /**
12554
+ * @internal
12555
+ */
12506
12556
  addManual(filename, message) {
12507
12557
  if (!(filename in this.result)) {
12508
12558
  this.result[filename] = [];
12509
12559
  }
12510
12560
  this.result[filename].push(message);
12511
12561
  }
12562
+ /**
12563
+ * @internal
12564
+ */
12512
12565
  save(sources) {
12513
12566
  const report = {
12514
12567
  valid: this.isValid(),
12515
12568
  results: Object.keys(this.result).map((filePath) => {
12516
- const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
12569
+ const messages = Array.from(this.result[filePath], freeze).toSorted(messageSort);
12517
12570
  const source = (sources ?? []).find((source2) => filePath === source2.filename);
12518
12571
  return {
12519
12572
  filePath,
@@ -12530,6 +12583,9 @@ class Reporter {
12530
12583
  report.warningCount = sumWarnings(report.results);
12531
12584
  return report;
12532
12585
  }
12586
+ /**
12587
+ * @internal
12588
+ */
12533
12589
  isValid() {
12534
12590
  const numErrors = Object.values(this.result).reduce((sum, messages) => {
12535
12591
  return sum + countErrors(messages);
@@ -12592,6 +12648,7 @@ class ParserError extends Error {
12592
12648
  location;
12593
12649
  constructor(location, message) {
12594
12650
  super(message);
12651
+ this.name = "ParserError";
12595
12652
  this.location = location;
12596
12653
  }
12597
12654
  }
@@ -12748,7 +12805,7 @@ class Parser {
12748
12805
  const tokens = Array.from(
12749
12806
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location)
12750
12807
  );
12751
- const endToken = tokens.slice(-1)[0];
12808
+ const endToken = tokens.at(-1);
12752
12809
  const closeOptional = this.closeOptional(startToken);
12753
12810
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
12754
12811
  const node = HtmlElement.fromTokens(
@@ -12866,7 +12923,7 @@ class Parser {
12866
12923
  const endTokens = Array.from(
12867
12924
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location)
12868
12925
  );
12869
- endToken = endTokens.slice(-1)[0];
12926
+ endToken = endTokens.at(-1);
12870
12927
  const selfClosed = endToken.data[0] === "/>";
12871
12928
  if (tagClosed) {
12872
12929
  startToken = last;
@@ -13148,7 +13205,7 @@ class Parser {
13148
13205
  this.event.once("*", cb);
13149
13206
  }
13150
13207
  trigger(event, data) {
13151
- if (typeof data.location === "undefined") {
13208
+ if (data.location === void 0) {
13152
13209
  throw new Error("Triggered event must contain location");
13153
13210
  }
13154
13211
  this.event.trigger(event, data);
@@ -13276,7 +13333,9 @@ class Engine {
13276
13333
  }
13277
13334
  lines.push({ event, data });
13278
13335
  });
13279
- source.forEach((src) => parser.parseHtml(src));
13336
+ for (const src of source) {
13337
+ parser.parseHtml(src);
13338
+ }
13280
13339
  return lines;
13281
13340
  }
13282
13341
  dumpTokens(source) {
@@ -13741,7 +13800,7 @@ const entities = {
13741
13800
  "&": "&amp;"
13742
13801
  };
13743
13802
  function xmlescape(src) {
13744
- return src.toString().replace(/[><'"&]/g, (match) => {
13803
+ return src.toString().replaceAll(/[><'"&]/g, (match) => {
13745
13804
  return entities[match];
13746
13805
  });
13747
13806
  }
@@ -13761,11 +13820,11 @@ function checkstyleFormatter(results) {
13761
13820
  `;
13762
13821
  output += `<checkstyle version="4.3">
13763
13822
  `;
13764
- results.forEach((result) => {
13823
+ for (const result of results) {
13765
13824
  const messages = result.messages;
13766
13825
  output += ` <file name="${xmlescape(result.filePath)}">
13767
13826
  `;
13768
- messages.forEach((message) => {
13827
+ for (const message of messages) {
13769
13828
  const ruleId = xmlescape(`htmlvalidate.rules.${message.ruleId}`);
13770
13829
  output += " ";
13771
13830
  output += [
@@ -13776,9 +13835,9 @@ function checkstyleFormatter(results) {
13776
13835
  `source="${ruleId}" />`
13777
13836
  ].join(" ");
13778
13837
  output += "\n";
13779
- });
13838
+ }
13780
13839
  output += " </file>\n";
13781
- });
13840
+ }
13782
13841
  output += "</checkstyle>\n";
13783
13842
  return output;
13784
13843
  }
@@ -13848,11 +13907,11 @@ function codeFrameColumns(rawLines, loc) {
13848
13907
  if (hasMarker) {
13849
13908
  let markerLine = "";
13850
13909
  if (Array.isArray(hasMarker)) {
13851
- const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
13910
+ const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replaceAll(/[^\t]/g, " ");
13852
13911
  const numberOfMarkers = hasMarker[1] || 1;
13853
13912
  markerLine = [
13854
13913
  "\n ",
13855
- gutter.replace(/\d/g, " "),
13914
+ gutter.replaceAll(/\d/g, " "),
13856
13915
  " ",
13857
13916
  markerSpacing,
13858
13917
  "^".repeat(numberOfMarkers)
@@ -13990,10 +14049,10 @@ function stylish(results) {
13990
14049
  function textFormatter(results) {
13991
14050
  let output = "";
13992
14051
  let total = 0;
13993
- results.forEach((result) => {
14052
+ for (const result of results) {
13994
14053
  const messages = result.messages;
13995
14054
  if (messages.length === 0) {
13996
- return;
14055
+ continue;
13997
14056
  }
13998
14057
  total += messages.length;
13999
14058
  output += messages.map((message) => {
@@ -14009,7 +14068,7 @@ function textFormatter(results) {
14009
14068
  return `${location}: ${messageType} [${message.ruleId}] ${message.message}
14010
14069
  `;
14011
14070
  }).join("");
14012
- });
14071
+ }
14013
14072
  return total > 0 ? output : "";
14014
14073
  }
14015
14074
  const formatter = textFormatter;
@@ -14841,7 +14900,7 @@ var ignoreExports = /*@__PURE__*/ requireIgnore();
14841
14900
  var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14842
14901
 
14843
14902
  const engines = {
14844
- node: "^20.19.0 || >= 22.12.0"
14903
+ node: "^20.19.0 || >= 22.16.0"
14845
14904
  };
14846
14905
 
14847
14906
  var workerPath = "./jest-worker.js";