html-validate 10.8.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 +333 -142
  8. package/dist/cjs/core.js.map +1 -1
  9. package/dist/cjs/elements.js +30 -18
  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 +333 -142
  25. package/dist/esm/core.js.map +1 -1
  26. package/dist/esm/elements.js +30 -18
  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) {
@@ -3999,7 +4019,7 @@ class Rule {
3999
4019
  }
4000
4020
  }
4001
4021
 
4002
- const defaults$A = {
4022
+ const defaults$B = {
4003
4023
  allowExternal: true,
4004
4024
  allowRelative: true,
4005
4025
  allowAbsolute: true,
@@ -4043,7 +4063,7 @@ class AllowedLinks extends Rule {
4043
4063
  allowRelative;
4044
4064
  allowAbsolute;
4045
4065
  constructor(options) {
4046
- super({ ...defaults$A, ...options });
4066
+ super({ ...defaults$B, ...options });
4047
4067
  this.allowExternal = parseAllow(this.options.allowExternal);
4048
4068
  this.allowRelative = parseAllow(this.options.allowRelative);
4049
4069
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4211,7 +4231,7 @@ class AllowedLinks extends Rule {
4211
4231
  }
4212
4232
  }
4213
4233
 
4214
- const defaults$z = {
4234
+ const defaults$A = {
4215
4235
  accessible: true
4216
4236
  };
4217
4237
  function findByTarget(target, siblings) {
@@ -4241,7 +4261,7 @@ function getDescription$1(context) {
4241
4261
  }
4242
4262
  class AreaAlt extends Rule {
4243
4263
  constructor(options) {
4244
- super({ ...defaults$z, ...options });
4264
+ super({ ...defaults$A, ...options });
4245
4265
  }
4246
4266
  static schema() {
4247
4267
  return {
@@ -4320,7 +4340,7 @@ class AriaHiddenBody extends Rule {
4320
4340
  }
4321
4341
  }
4322
4342
 
4323
- const defaults$y = {
4343
+ const defaults$z = {
4324
4344
  allowAnyNamable: false,
4325
4345
  elements: {
4326
4346
  include: null,
@@ -4368,7 +4388,7 @@ function isValidUsage(target, meta) {
4368
4388
  }
4369
4389
  class AriaLabelMisuse extends Rule {
4370
4390
  constructor(options) {
4371
- super({ ...defaults$y, ...options });
4391
+ super({ ...defaults$z, ...options });
4372
4392
  }
4373
4393
  static schema() {
4374
4394
  return {
@@ -4480,8 +4500,7 @@ class AriaLabelMisuse extends Rule {
4480
4500
  class ConfigError extends UserError {
4481
4501
  constructor(message, nested) {
4482
4502
  super(message, nested);
4483
- Error.captureStackTrace(this, ConfigError);
4484
- this.name = ConfigError.name;
4503
+ this.name = "ConfigError";
4485
4504
  }
4486
4505
  }
4487
4506
 
@@ -4537,14 +4556,14 @@ class CaseStyle {
4537
4556
  }
4538
4557
  }
4539
4558
 
4540
- const defaults$x = {
4559
+ const defaults$y = {
4541
4560
  style: "lowercase",
4542
4561
  ignoreForeign: true
4543
4562
  };
4544
4563
  class AttrCase extends Rule {
4545
4564
  style;
4546
4565
  constructor(options) {
4547
- super({ ...defaults$x, ...options });
4566
+ super({ ...defaults$y, ...options });
4548
4567
  this.style = new CaseStyle(this.options.style, "attr-case");
4549
4568
  }
4550
4569
  static schema() {
@@ -4585,7 +4604,7 @@ class AttrCase extends Rule {
4585
4604
  if (event.originalAttribute) {
4586
4605
  return;
4587
4606
  }
4588
- const letters = event.key.replace(/[^a-z]+/gi, "");
4607
+ const letters = event.key.replaceAll(/[^a-z]+/gi, "");
4589
4608
  if (this.style.match(letters)) {
4590
4609
  return;
4591
4610
  }
@@ -4658,6 +4677,7 @@ class InvalidTokenError extends Error {
4658
4677
  location;
4659
4678
  constructor(location, message) {
4660
4679
  super(message);
4680
+ this.name = "InvalidTokenError";
4661
4681
  this.location = location;
4662
4682
  }
4663
4683
  }
@@ -4767,16 +4787,26 @@ class Lexer {
4767
4787
  */
4768
4788
  enter(context, state, data) {
4769
4789
  if (state === State.TAG && data?.[0].startsWith("<")) {
4770
- if (data[0] === "<script") {
4771
- context.contentModel = ContentModel.SCRIPT;
4772
- } else if (data[0] === "<style") {
4773
- context.contentModel = ContentModel.STYLE;
4774
- } else if (data[0] === "<textarea") {
4775
- context.contentModel = ContentModel.TEXTAREA;
4776
- } else if (data[0] === "<title") {
4777
- context.contentModel = ContentModel.TITLE;
4778
- } else {
4779
- context.contentModel = ContentModel.TEXT;
4790
+ switch (data[0]) {
4791
+ case "<script": {
4792
+ context.contentModel = ContentModel.SCRIPT;
4793
+ break;
4794
+ }
4795
+ case "<style": {
4796
+ context.contentModel = ContentModel.STYLE;
4797
+ break;
4798
+ }
4799
+ case "<textarea": {
4800
+ context.contentModel = ContentModel.TEXTAREA;
4801
+ break;
4802
+ }
4803
+ case "<title": {
4804
+ context.contentModel = ContentModel.TITLE;
4805
+ break;
4806
+ }
4807
+ default: {
4808
+ context.contentModel = ContentModel.TEXT;
4809
+ }
4780
4810
  }
4781
4811
  }
4782
4812
  }
@@ -4949,7 +4979,7 @@ class AttrDelimiter extends Rule {
4949
4979
  }
4950
4980
 
4951
4981
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4952
- const defaults$w = {
4982
+ const defaults$x = {
4953
4983
  pattern: DEFAULT_PATTERN,
4954
4984
  ignoreForeign: true
4955
4985
  };
@@ -4982,7 +5012,7 @@ function generateDescription(name, pattern) {
4982
5012
  class AttrPattern extends Rule {
4983
5013
  pattern;
4984
5014
  constructor(options) {
4985
- super({ ...defaults$w, ...options });
5015
+ super({ ...defaults$x, ...options });
4986
5016
  this.pattern = generateRegexp(this.options.pattern);
4987
5017
  }
4988
5018
  static schema() {
@@ -5029,7 +5059,7 @@ class AttrPattern extends Rule {
5029
5059
  }
5030
5060
  }
5031
5061
 
5032
- const defaults$v = {
5062
+ const defaults$w = {
5033
5063
  style: "auto",
5034
5064
  unquoted: false
5035
5065
  };
@@ -5095,7 +5125,7 @@ class AttrQuotes extends Rule {
5095
5125
  };
5096
5126
  }
5097
5127
  constructor(options) {
5098
- super({ ...defaults$v, ...options });
5128
+ super({ ...defaults$w, ...options });
5099
5129
  this.style = parseStyle$3(this.options.style);
5100
5130
  }
5101
5131
  setup() {
@@ -5177,10 +5207,10 @@ class AttrSpacing extends Rule {
5177
5207
 
5178
5208
  function pick(attr) {
5179
5209
  const result = {};
5180
- if (typeof attr.enum !== "undefined") {
5210
+ if (attr.enum !== void 0) {
5181
5211
  result.enum = attr.enum;
5182
5212
  }
5183
- if (typeof attr.boolean !== "undefined") {
5213
+ if (attr.boolean !== void 0) {
5184
5214
  result.boolean = attr.boolean;
5185
5215
  }
5186
5216
  return result;
@@ -5251,13 +5281,13 @@ class AttributeAllowedValues extends Rule {
5251
5281
  }
5252
5282
  }
5253
5283
 
5254
- const defaults$u = {
5284
+ const defaults$v = {
5255
5285
  style: "omit"
5256
5286
  };
5257
5287
  class AttributeBooleanStyle extends Rule {
5258
5288
  hasInvalidStyle;
5259
5289
  constructor(options) {
5260
- super({ ...defaults$u, ...options });
5290
+ super({ ...defaults$v, ...options });
5261
5291
  this.hasInvalidStyle = parseStyle$2(this.options.style);
5262
5292
  }
5263
5293
  static schema() {
@@ -5327,13 +5357,13 @@ function reportMessage$1(attr, style) {
5327
5357
  return "";
5328
5358
  }
5329
5359
 
5330
- const defaults$t = {
5360
+ const defaults$u = {
5331
5361
  style: "omit"
5332
5362
  };
5333
5363
  class AttributeEmptyStyle extends Rule {
5334
5364
  hasInvalidStyle;
5335
5365
  constructor(options) {
5336
- super({ ...defaults$t, ...options });
5366
+ super({ ...defaults$u, ...options });
5337
5367
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5338
5368
  }
5339
5369
  static schema() {
@@ -5450,7 +5480,7 @@ class AttributeMisuse extends Rule {
5450
5480
  }
5451
5481
  }
5452
5482
 
5453
- const defaults$s = {
5483
+ const defaults$t = {
5454
5484
  preferred: void 0
5455
5485
  };
5456
5486
  function isPasswordInput(event) {
@@ -5467,7 +5497,7 @@ function isGroupingToken(token) {
5467
5497
  class AutocompletePassword extends Rule {
5468
5498
  preferred;
5469
5499
  constructor(options) {
5470
- super({ ...defaults$s, ...options });
5500
+ super({ ...defaults$t, ...options });
5471
5501
  this.preferred = options.preferred?.toLowerCase();
5472
5502
  }
5473
5503
  static schema() {
@@ -5697,7 +5727,7 @@ class BasePatternRule extends Rule {
5697
5727
  }
5698
5728
  }
5699
5729
 
5700
- const defaults$r = {
5730
+ const defaults$s = {
5701
5731
  pattern: "kebabcase"
5702
5732
  };
5703
5733
  class ClassPattern extends BasePatternRule {
@@ -5705,7 +5735,7 @@ class ClassPattern extends BasePatternRule {
5705
5735
  super({
5706
5736
  ruleId: "class-pattern",
5707
5737
  attr: "class",
5708
- options: { ...defaults$r, ...options },
5738
+ options: { ...defaults$s, ...options },
5709
5739
  allowedPatterns: patternNames
5710
5740
  // allow all patterns
5711
5741
  });
@@ -5855,13 +5885,13 @@ class CloseOrder extends Rule {
5855
5885
  }
5856
5886
  }
5857
5887
 
5858
- const defaults$q = {
5888
+ const defaults$r = {
5859
5889
  include: null,
5860
5890
  exclude: null
5861
5891
  };
5862
5892
  class Deprecated extends Rule {
5863
5893
  constructor(options) {
5864
- super({ ...defaults$q, ...options });
5894
+ super({ ...defaults$r, ...options });
5865
5895
  }
5866
5896
  static schema() {
5867
5897
  return {
@@ -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;
@@ -5969,6 +5999,137 @@ function prettySource(source) {
5969
5999
  }
5970
6000
  }
5971
6001
 
6002
+ function quote(value, char = '"') {
6003
+ return `${char}${value}${char}`;
6004
+ }
6005
+
6006
+ const defaults$q = {
6007
+ classes: []
6008
+ };
6009
+ function isRelevant$6(event) {
6010
+ return event.key.toLowerCase() === "class";
6011
+ }
6012
+ function normalizeEntry(entry) {
6013
+ const { class: className, message, replacement = [], url } = entry;
6014
+ return {
6015
+ class: className,
6016
+ message,
6017
+ replacement: Array.isArray(replacement) ? replacement : [replacement],
6018
+ url
6019
+ };
6020
+ }
6021
+ function formatDeprecatedMessage(className, entry) {
6022
+ let message = `class "${className}" is deprecated`;
6023
+ if (entry.replacement.length > 0) {
6024
+ const joined = naturalJoin(
6025
+ entry.replacement.map((r) => quote(r)),
6026
+ "or"
6027
+ );
6028
+ message += ` and replaced with ${joined}`;
6029
+ }
6030
+ if (entry.message) {
6031
+ message += `: ${entry.message}`;
6032
+ }
6033
+ return message;
6034
+ }
6035
+ function formatDocumentationDescription(context) {
6036
+ const text = [];
6037
+ const className = context.class;
6038
+ let description = `The class \`${className}\` is deprecated and should not be used`;
6039
+ if (context.message) {
6040
+ description += `: ${context.message}.`;
6041
+ } else {
6042
+ description += ".";
6043
+ }
6044
+ text.push(description);
6045
+ if (context.replacement.length === 1) {
6046
+ text.push(`Use the replacement class ${quote(context.replacement[0], "`")} instead.`);
6047
+ } else if (context.replacement.length > 1) {
6048
+ const listItems = context.replacement.map((r) => `- ${quote(r, "`")}`);
6049
+ text.push(`Use one of the following replacement classes instead:
6050
+ ${listItems.join("\n")}`);
6051
+ }
6052
+ if (context.url) {
6053
+ text.push(`For details see: ${context.url}`);
6054
+ }
6055
+ return text.join("\n\n");
6056
+ }
6057
+ class DeprecatedClass extends Rule {
6058
+ deprecatedMap;
6059
+ constructor(options) {
6060
+ super({ ...defaults$q, ...options });
6061
+ const { classes } = this.options;
6062
+ this.deprecatedMap = new Map(classes.map((entry) => [entry.class, normalizeEntry(entry)]));
6063
+ }
6064
+ static schema() {
6065
+ return {
6066
+ classes: {
6067
+ type: "array",
6068
+ items: {
6069
+ type: "object",
6070
+ properties: {
6071
+ class: {
6072
+ type: "string"
6073
+ },
6074
+ message: {
6075
+ type: "string"
6076
+ },
6077
+ replacement: {
6078
+ anyOf: [
6079
+ {
6080
+ type: "string"
6081
+ },
6082
+ {
6083
+ type: "array",
6084
+ items: {
6085
+ type: "string"
6086
+ }
6087
+ }
6088
+ ]
6089
+ },
6090
+ url: {
6091
+ type: "string"
6092
+ }
6093
+ },
6094
+ required: ["class"],
6095
+ additionalProperties: false
6096
+ }
6097
+ }
6098
+ };
6099
+ }
6100
+ documentation(context) {
6101
+ return {
6102
+ description: formatDocumentationDescription(context),
6103
+ url: "https://html-validate.org/rules/deprecated-class.html"
6104
+ };
6105
+ }
6106
+ setup() {
6107
+ this.on("attr", isRelevant$6, (event) => {
6108
+ const { value, valueLocation, target } = event;
6109
+ const classes = new DOMTokenList(value, valueLocation);
6110
+ for (const { item, location } of classes.iterator()) {
6111
+ const deprecatedEntry = this.deprecatedMap.get(item);
6112
+ if (!deprecatedEntry) {
6113
+ continue;
6114
+ }
6115
+ const message = formatDeprecatedMessage(item, deprecatedEntry);
6116
+ const context = {
6117
+ class: item,
6118
+ message: deprecatedEntry.message ?? null,
6119
+ replacement: deprecatedEntry.replacement,
6120
+ url: deprecatedEntry.url ?? null
6121
+ };
6122
+ this.report({
6123
+ node: target,
6124
+ message,
6125
+ location,
6126
+ context
6127
+ });
6128
+ }
6129
+ });
6130
+ }
6131
+ }
6132
+
5972
6133
  class DeprecatedRule extends Rule {
5973
6134
  documentation(context) {
5974
6135
  const preamble = context ? `The rule "${context}"` : "This rule";
@@ -6095,7 +6256,7 @@ class ElementCase extends Rule {
6095
6256
  });
6096
6257
  }
6097
6258
  validateCase(target, targetLocation) {
6098
- const letters = target.tagName.replace(/[^a-z]+/gi, "");
6259
+ const letters = target.tagName.replaceAll(/[^a-z]+/gi, "");
6099
6260
  if (!this.style.match(letters)) {
6100
6261
  const location = sliceLocation(targetLocation, 1);
6101
6262
  this.report(target, `Element "${target.tagName}" should be ${this.style.name}`, location);
@@ -6699,8 +6860,8 @@ function haveName(name) {
6699
6860
  return typeof name === "string" && name !== "";
6700
6861
  }
6701
6862
  function allowSharedName(node, shared) {
6702
- const type = node.getAttribute("type");
6703
- return Boolean(type?.valueMatches(shared, false));
6863
+ const type = getControlType(node);
6864
+ return shared.includes(type);
6704
6865
  }
6705
6866
  function isInputHidden(element) {
6706
6867
  return element.is("input") && element.getAttributeValue("type") === "hidden";
@@ -6708,6 +6869,13 @@ function isInputHidden(element) {
6708
6869
  function isInputCheckbox(element) {
6709
6870
  return element.is("input") && element.getAttributeValue("type") === "checkbox";
6710
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
+ }
6711
6879
  function isCheckboxWithDefault(control, previous, options) {
6712
6880
  const { allowCheckboxDefault } = options;
6713
6881
  if (!allowCheckboxDefault) {
@@ -6845,7 +7013,7 @@ class FormDupName extends Rule {
6845
7013
  validateSharedName(control, group, attr, name) {
6846
7014
  const uniqueElements = this.getUniqueElements(group);
6847
7015
  const sharedElements = this.getSharedElements(group);
6848
- const type = control.getAttributeValue("type") ?? "";
7016
+ const type = getControlType(control);
6849
7017
  if (uniqueElements.has(name) || sharedElements.has(name) && sharedElements.get(name) !== type) {
6850
7018
  const context = {
6851
7019
  name,
@@ -6907,7 +7075,7 @@ function isRelevant$5(event) {
6907
7075
  function extractLevel(node) {
6908
7076
  const match = /^[hH](\d)$/.exec(node.tagName);
6909
7077
  if (match) {
6910
- return parseInt(match[1], 10);
7078
+ return Number.parseInt(match[1], 10);
6911
7079
  } else {
6912
7080
  return null;
6913
7081
  }
@@ -6920,7 +7088,7 @@ function parseMaxInitial(value) {
6920
7088
  if (!match) {
6921
7089
  return 1;
6922
7090
  }
6923
- return parseInt(match[1], 10);
7091
+ return Number.parseInt(match[1], 10);
6924
7092
  }
6925
7093
  class HeadingLevel extends Rule {
6926
7094
  minInitialRank;
@@ -7069,10 +7237,10 @@ class HeadingLevel extends Rule {
7069
7237
  this.stack.pop();
7070
7238
  }
7071
7239
  getPrevRoot() {
7072
- return this.stack[this.stack.length - 2];
7240
+ return this.stack.at(-2);
7073
7241
  }
7074
7242
  getCurrentRoot() {
7075
- return this.stack[this.stack.length - 1];
7243
+ return this.stack.at(-1);
7076
7244
  }
7077
7245
  isSectioningRoot(node) {
7078
7246
  const context = {
@@ -7669,7 +7837,7 @@ function parseContent(text) {
7669
7837
  const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7670
7838
  if (match) {
7671
7839
  return {
7672
- delay: parseInt(match[1], 10),
7840
+ delay: Number.parseInt(match[1], 10),
7673
7841
  url: match[2]
7674
7842
  };
7675
7843
  } else {
@@ -7993,13 +8161,12 @@ class NoDupClass extends Rule {
7993
8161
  }
7994
8162
  const classes = new DOMTokenList(event.value, event.valueLocation);
7995
8163
  const unique = /* @__PURE__ */ new Set();
7996
- classes.forEach((cur, index) => {
7997
- if (unique.has(cur)) {
7998
- const location = classes.location(index);
7999
- 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);
8000
8167
  }
8001
- unique.add(cur);
8002
- });
8168
+ unique.add(item);
8169
+ }
8003
8170
  });
8004
8171
  }
8005
8172
  }
@@ -8731,7 +8898,7 @@ class NoUnusedDisable extends Rule {
8731
8898
  setup() {
8732
8899
  }
8733
8900
  reportUnused(unused, options, location) {
8734
- const tokens = new DOMTokenList(options.replace(/,/g, " "), location);
8901
+ const tokens = new DOMTokenList(options.replaceAll(",", " "), location);
8735
8902
  for (const ruleId of unused) {
8736
8903
  const index = tokens.indexOf(ruleId);
8737
8904
  const tokenLocation = index >= 0 ? tokens.location(index) : location;
@@ -9052,19 +9219,19 @@ const supportSri = {
9052
9219
  link: "href",
9053
9220
  script: "src"
9054
9221
  };
9055
- const supportedRel = ["stylesheet", "preload", "modulepreload"];
9056
- const supportedPreload = ["style", "script"];
9222
+ const supportedRel = /* @__PURE__ */ new Set(["stylesheet", "preload", "modulepreload"]);
9223
+ const supportedPreload = /* @__PURE__ */ new Set(["style", "script"]);
9057
9224
  function linkSupportsSri(node) {
9058
9225
  const rel = node.getAttribute("rel");
9059
9226
  if (typeof rel?.value !== "string") {
9060
9227
  return false;
9061
9228
  }
9062
- if (!supportedRel.includes(rel.value)) {
9229
+ if (!supportedRel.has(rel.value)) {
9063
9230
  return false;
9064
9231
  }
9065
9232
  if (rel.value === "preload") {
9066
9233
  const as = node.getAttribute("as");
9067
- return typeof as?.value === "string" && supportedPreload.includes(as.value);
9234
+ return typeof as?.value === "string" && supportedPreload.has(as.value);
9068
9235
  }
9069
9236
  return true;
9070
9237
  }
@@ -9181,13 +9348,13 @@ class ScriptElement extends Rule {
9181
9348
  }
9182
9349
  }
9183
9350
 
9184
- const javascript = [
9351
+ const javascript = /* @__PURE__ */ new Set([
9185
9352
  "",
9186
9353
  "application/ecmascript",
9187
9354
  "application/javascript",
9188
9355
  "text/ecmascript",
9189
9356
  "text/javascript"
9190
- ];
9357
+ ]);
9191
9358
  class ScriptType extends Rule {
9192
9359
  documentation() {
9193
9360
  return {
@@ -9218,7 +9385,7 @@ class ScriptType extends Rule {
9218
9385
  }
9219
9386
  isJavascript(mime) {
9220
9387
  const type = mime.replace(/;.*/, "");
9221
- return javascript.includes(type);
9388
+ return javascript.has(type);
9222
9389
  }
9223
9390
  }
9224
9391
 
@@ -9794,7 +9961,7 @@ class UnknownCharReference extends Rule {
9794
9961
  }
9795
9962
 
9796
9963
  const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
9797
- const fieldNames1 = [
9964
+ const fieldNames1 = /* @__PURE__ */ new Set([
9798
9965
  "name",
9799
9966
  "honorific-prefix",
9800
9967
  "given-name",
@@ -9839,8 +10006,8 @@ const fieldNames1 = [
9839
10006
  "sex",
9840
10007
  "url",
9841
10008
  "photo"
9842
- ];
9843
- const fieldNames2 = [
10009
+ ]);
10010
+ const fieldNames2 = /* @__PURE__ */ new Set([
9844
10011
  "tel",
9845
10012
  "tel-country-code",
9846
10013
  "tel-national",
@@ -9851,7 +10018,7 @@ const fieldNames2 = [
9851
10018
  "tel-extension",
9852
10019
  "email",
9853
10020
  "impp"
9854
- ];
10021
+ ]);
9855
10022
  const fieldNameGroup = {
9856
10023
  name: "text",
9857
10024
  "honorific-prefix": "text",
@@ -9916,14 +10083,14 @@ function matchHint(token) {
9916
10083
  return token === "shipping" || token === "billing";
9917
10084
  }
9918
10085
  function matchFieldNames1(token) {
9919
- return fieldNames1.includes(token);
10086
+ return fieldNames1.has(token);
9920
10087
  }
9921
10088
  function matchContact(token) {
9922
10089
  const haystack = ["home", "work", "mobile", "fax", "pager"];
9923
10090
  return haystack.includes(token);
9924
10091
  }
9925
10092
  function matchFieldNames2(token) {
9926
- return fieldNames2.includes(token);
10093
+ return fieldNames2.has(token);
9927
10094
  }
9928
10095
  function matchWebauthn(token) {
9929
10096
  return token === "webauthn";
@@ -10594,11 +10761,13 @@ class H32 extends Rule {
10594
10761
  setup() {
10595
10762
  const formTags = this.getTagsWithProperty("form");
10596
10763
  const formSelector = formTags.join(",");
10764
+ const submitButtonTags = this.getTagsWithProperty("submitButton");
10765
+ const submitButtonSelector = submitButtonTags.join(",");
10597
10766
  this.on("dom:ready", (event) => {
10598
10767
  const { document } = event;
10599
10768
  const forms = document.querySelectorAll(formSelector);
10600
10769
  for (const form of forms) {
10601
- if (hasNestedSubmit(form)) {
10770
+ if (hasNestedSubmit(form, submitButtonSelector)) {
10602
10771
  continue;
10603
10772
  }
10604
10773
  if (hasAssociatedSubmit(document, form)) {
@@ -10610,15 +10779,15 @@ class H32 extends Rule {
10610
10779
  }
10611
10780
  }
10612
10781
  function isSubmit(node) {
10613
- const type = node.getAttribute("type");
10614
- return !type || type.valueMatches(/submit|image/);
10782
+ const meta = node.meta;
10783
+ return Boolean(meta?.submitButton);
10615
10784
  }
10616
10785
  function isAssociated(id, node) {
10617
10786
  const form = node.getAttribute("form");
10618
10787
  return Boolean(form?.valueMatches(id, true));
10619
10788
  }
10620
- function hasNestedSubmit(form) {
10621
- 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"));
10622
10791
  return matches.length > 0;
10623
10792
  }
10624
10793
  function hasAssociatedSubmit(document, form) {
@@ -10909,6 +11078,7 @@ const bundledRules = {
10909
11078
  "close-attr": CloseAttr,
10910
11079
  "close-order": CloseOrder,
10911
11080
  deprecated: Deprecated,
11081
+ "deprecated-class": DeprecatedClass,
10912
11082
  "deprecated-rule": DeprecatedRule,
10913
11083
  "doctype-html": NoStyleTag$1,
10914
11084
  "doctype-style": DoctypeStyle,
@@ -11166,11 +11336,11 @@ function dumpTree(root) {
11166
11336
  } else {
11167
11337
  lines.push("(root)");
11168
11338
  }
11169
- node.childElements.forEach((child, index) => {
11339
+ for (const [index, child] of node.childElements.entries()) {
11170
11340
  const s = lastSibling ? " " : "\u2502";
11171
11341
  const i = level > 0 ? `${indent}${s} ` : "";
11172
11342
  writeNode(child, level + 1, i, index);
11173
- });
11343
+ }
11174
11344
  }
11175
11345
  writeNode(root, 0, "", 0);
11176
11346
  return lines;
@@ -12285,7 +12455,7 @@ class EventHandler {
12285
12455
  }
12286
12456
 
12287
12457
  const name = "html-validate";
12288
- const version = "10.8.0";
12458
+ const version = "10.10.0";
12289
12459
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12290
12460
 
12291
12461
  function freeze(src) {
@@ -12301,7 +12471,13 @@ function isThenableArray(value) {
12301
12471
  return isThenable(value[0]);
12302
12472
  }
12303
12473
  class Reporter {
12474
+ /**
12475
+ * @internal
12476
+ */
12304
12477
  result;
12478
+ /**
12479
+ * @internal
12480
+ */
12305
12481
  constructor() {
12306
12482
  this.result = {};
12307
12483
  }
@@ -12314,16 +12490,16 @@ class Reporter {
12314
12490
  }
12315
12491
  const valid = reports.every((report) => report.valid);
12316
12492
  const merged = {};
12317
- reports.forEach((report) => {
12318
- report.results.forEach((result) => {
12493
+ for (const report of reports) {
12494
+ for (const result of report.results) {
12319
12495
  const key = result.filePath;
12320
12496
  if (key in merged) {
12321
12497
  merged[key].messages = [...merged[key].messages, ...result.messages];
12322
12498
  } else {
12323
12499
  merged[key] = { ...result };
12324
12500
  }
12325
- });
12326
- });
12501
+ }
12502
+ }
12327
12503
  const results = Object.values(merged).map((result) => {
12328
12504
  result.errorCount = countErrors(result.messages);
12329
12505
  result.warningCount = countWarnings(result.messages);
@@ -12336,8 +12512,11 @@ class Reporter {
12336
12512
  warningCount: sumWarnings(results)
12337
12513
  };
12338
12514
  }
12339
- /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12340
- add(rule, message, severity, node, location, context) {
12515
+ /**
12516
+ * @internal
12517
+ */
12518
+ add(options) {
12519
+ const { rule, message, severity, node, location, context } = options;
12341
12520
  if (!(location.filename in this.result)) {
12342
12521
  this.result[location.filename] = [];
12343
12522
  }
@@ -12362,17 +12541,23 @@ class Reporter {
12362
12541
  }
12363
12542
  this.result[location.filename].push(entry);
12364
12543
  }
12544
+ /**
12545
+ * @internal
12546
+ */
12365
12547
  addManual(filename, message) {
12366
12548
  if (!(filename in this.result)) {
12367
12549
  this.result[filename] = [];
12368
12550
  }
12369
12551
  this.result[filename].push(message);
12370
12552
  }
12553
+ /**
12554
+ * @internal
12555
+ */
12371
12556
  save(sources) {
12372
12557
  const report = {
12373
12558
  valid: this.isValid(),
12374
12559
  results: Object.keys(this.result).map((filePath) => {
12375
- const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
12560
+ const messages = Array.from(this.result[filePath], freeze).toSorted(messageSort);
12376
12561
  const source = (sources ?? []).find((source2) => filePath === source2.filename);
12377
12562
  return {
12378
12563
  filePath,
@@ -12389,6 +12574,9 @@ class Reporter {
12389
12574
  report.warningCount = sumWarnings(report.results);
12390
12575
  return report;
12391
12576
  }
12577
+ /**
12578
+ * @internal
12579
+ */
12392
12580
  isValid() {
12393
12581
  const numErrors = Object.values(this.result).reduce((sum, messages) => {
12394
12582
  return sum + countErrors(messages);
@@ -12451,6 +12639,7 @@ class ParserError extends Error {
12451
12639
  location;
12452
12640
  constructor(location, message) {
12453
12641
  super(message);
12642
+ this.name = "ParserError";
12454
12643
  this.location = location;
12455
12644
  }
12456
12645
  }
@@ -12607,7 +12796,7 @@ class Parser {
12607
12796
  const tokens = Array.from(
12608
12797
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location)
12609
12798
  );
12610
- const endToken = tokens.slice(-1)[0];
12799
+ const endToken = tokens.at(-1);
12611
12800
  const closeOptional = this.closeOptional(startToken);
12612
12801
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
12613
12802
  const node = HtmlElement.fromTokens(
@@ -12725,7 +12914,7 @@ class Parser {
12725
12914
  const endTokens = Array.from(
12726
12915
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location)
12727
12916
  );
12728
- endToken = endTokens.slice(-1)[0];
12917
+ endToken = endTokens.at(-1);
12729
12918
  const selfClosed = endToken.data[0] === "/>";
12730
12919
  if (tagClosed) {
12731
12920
  startToken = last;
@@ -13007,7 +13196,7 @@ class Parser {
13007
13196
  this.event.once("*", cb);
13008
13197
  }
13009
13198
  trigger(event, data) {
13010
- if (typeof data.location === "undefined") {
13199
+ if (data.location === void 0) {
13011
13200
  throw new Error("Triggered event must contain location");
13012
13201
  }
13013
13202
  this.event.trigger(event, data);
@@ -13135,7 +13324,9 @@ class Engine {
13135
13324
  }
13136
13325
  lines.push({ event, data });
13137
13326
  });
13138
- source.forEach((src) => parser.parseHtml(src));
13327
+ for (const src of source) {
13328
+ parser.parseHtml(src);
13329
+ }
13139
13330
  return lines;
13140
13331
  }
13141
13332
  dumpTokens(source) {
@@ -13600,7 +13791,7 @@ const entities = {
13600
13791
  "&": "&amp;"
13601
13792
  };
13602
13793
  function xmlescape(src) {
13603
- return src.toString().replace(/[><'"&]/g, (match) => {
13794
+ return src.toString().replaceAll(/[><'"&]/g, (match) => {
13604
13795
  return entities[match];
13605
13796
  });
13606
13797
  }
@@ -13620,11 +13811,11 @@ function checkstyleFormatter(results) {
13620
13811
  `;
13621
13812
  output += `<checkstyle version="4.3">
13622
13813
  `;
13623
- results.forEach((result) => {
13814
+ for (const result of results) {
13624
13815
  const messages = result.messages;
13625
13816
  output += ` <file name="${xmlescape(result.filePath)}">
13626
13817
  `;
13627
- messages.forEach((message) => {
13818
+ for (const message of messages) {
13628
13819
  const ruleId = xmlescape(`htmlvalidate.rules.${message.ruleId}`);
13629
13820
  output += " ";
13630
13821
  output += [
@@ -13635,9 +13826,9 @@ function checkstyleFormatter(results) {
13635
13826
  `source="${ruleId}" />`
13636
13827
  ].join(" ");
13637
13828
  output += "\n";
13638
- });
13829
+ }
13639
13830
  output += " </file>\n";
13640
- });
13831
+ }
13641
13832
  output += "</checkstyle>\n";
13642
13833
  return output;
13643
13834
  }
@@ -13707,11 +13898,11 @@ function codeFrameColumns(rawLines, loc) {
13707
13898
  if (hasMarker) {
13708
13899
  let markerLine = "";
13709
13900
  if (Array.isArray(hasMarker)) {
13710
- 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, " ");
13711
13902
  const numberOfMarkers = hasMarker[1] || 1;
13712
13903
  markerLine = [
13713
13904
  "\n ",
13714
- gutter.replace(/\d/g, " "),
13905
+ gutter.replaceAll(/\d/g, " "),
13715
13906
  " ",
13716
13907
  markerSpacing,
13717
13908
  "^".repeat(numberOfMarkers)
@@ -13849,10 +14040,10 @@ function stylish(results) {
13849
14040
  function textFormatter(results) {
13850
14041
  let output = "";
13851
14042
  let total = 0;
13852
- results.forEach((result) => {
14043
+ for (const result of results) {
13853
14044
  const messages = result.messages;
13854
14045
  if (messages.length === 0) {
13855
- return;
14046
+ continue;
13856
14047
  }
13857
14048
  total += messages.length;
13858
14049
  output += messages.map((message) => {
@@ -13868,7 +14059,7 @@ function textFormatter(results) {
13868
14059
  return `${location}: ${messageType} [${message.ruleId}] ${message.message}
13869
14060
  `;
13870
14061
  }).join("");
13871
- });
14062
+ }
13872
14063
  return total > 0 ? output : "";
13873
14064
  }
13874
14065
  const formatter = textFormatter;
@@ -14700,7 +14891,7 @@ var ignoreExports = /*@__PURE__*/ requireIgnore();
14700
14891
  var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14701
14892
 
14702
14893
  const engines = {
14703
- node: "^20.19.0 || >= 22.12.0"
14894
+ node: "^20.19.0 || >= 22.16.0"
14704
14895
  };
14705
14896
 
14706
14897
  var workerPath = "./jest-worker.js";