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/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) {
@@ -4008,7 +4028,7 @@ class Rule {
4008
4028
  }
4009
4029
  }
4010
4030
 
4011
- const defaults$A = {
4031
+ const defaults$B = {
4012
4032
  allowExternal: true,
4013
4033
  allowRelative: true,
4014
4034
  allowAbsolute: true,
@@ -4052,7 +4072,7 @@ class AllowedLinks extends Rule {
4052
4072
  allowRelative;
4053
4073
  allowAbsolute;
4054
4074
  constructor(options) {
4055
- super({ ...defaults$A, ...options });
4075
+ super({ ...defaults$B, ...options });
4056
4076
  this.allowExternal = parseAllow(this.options.allowExternal);
4057
4077
  this.allowRelative = parseAllow(this.options.allowRelative);
4058
4078
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4220,7 +4240,7 @@ class AllowedLinks extends Rule {
4220
4240
  }
4221
4241
  }
4222
4242
 
4223
- const defaults$z = {
4243
+ const defaults$A = {
4224
4244
  accessible: true
4225
4245
  };
4226
4246
  function findByTarget(target, siblings) {
@@ -4250,7 +4270,7 @@ function getDescription$1(context) {
4250
4270
  }
4251
4271
  class AreaAlt extends Rule {
4252
4272
  constructor(options) {
4253
- super({ ...defaults$z, ...options });
4273
+ super({ ...defaults$A, ...options });
4254
4274
  }
4255
4275
  static schema() {
4256
4276
  return {
@@ -4329,7 +4349,7 @@ class AriaHiddenBody extends Rule {
4329
4349
  }
4330
4350
  }
4331
4351
 
4332
- const defaults$y = {
4352
+ const defaults$z = {
4333
4353
  allowAnyNamable: false,
4334
4354
  elements: {
4335
4355
  include: null,
@@ -4377,7 +4397,7 @@ function isValidUsage(target, meta) {
4377
4397
  }
4378
4398
  class AriaLabelMisuse extends Rule {
4379
4399
  constructor(options) {
4380
- super({ ...defaults$y, ...options });
4400
+ super({ ...defaults$z, ...options });
4381
4401
  }
4382
4402
  static schema() {
4383
4403
  return {
@@ -4489,8 +4509,7 @@ class AriaLabelMisuse extends Rule {
4489
4509
  class ConfigError extends UserError {
4490
4510
  constructor(message, nested) {
4491
4511
  super(message, nested);
4492
- Error.captureStackTrace(this, ConfigError);
4493
- this.name = ConfigError.name;
4512
+ this.name = "ConfigError";
4494
4513
  }
4495
4514
  }
4496
4515
 
@@ -4546,14 +4565,14 @@ class CaseStyle {
4546
4565
  }
4547
4566
  }
4548
4567
 
4549
- const defaults$x = {
4568
+ const defaults$y = {
4550
4569
  style: "lowercase",
4551
4570
  ignoreForeign: true
4552
4571
  };
4553
4572
  class AttrCase extends Rule {
4554
4573
  style;
4555
4574
  constructor(options) {
4556
- super({ ...defaults$x, ...options });
4575
+ super({ ...defaults$y, ...options });
4557
4576
  this.style = new CaseStyle(this.options.style, "attr-case");
4558
4577
  }
4559
4578
  static schema() {
@@ -4594,7 +4613,7 @@ class AttrCase extends Rule {
4594
4613
  if (event.originalAttribute) {
4595
4614
  return;
4596
4615
  }
4597
- const letters = event.key.replace(/[^a-z]+/gi, "");
4616
+ const letters = event.key.replaceAll(/[^a-z]+/gi, "");
4598
4617
  if (this.style.match(letters)) {
4599
4618
  return;
4600
4619
  }
@@ -4667,6 +4686,7 @@ class InvalidTokenError extends Error {
4667
4686
  location;
4668
4687
  constructor(location, message) {
4669
4688
  super(message);
4689
+ this.name = "InvalidTokenError";
4670
4690
  this.location = location;
4671
4691
  }
4672
4692
  }
@@ -4776,16 +4796,26 @@ class Lexer {
4776
4796
  */
4777
4797
  enter(context, state, data) {
4778
4798
  if (state === State.TAG && data?.[0].startsWith("<")) {
4779
- if (data[0] === "<script") {
4780
- context.contentModel = ContentModel.SCRIPT;
4781
- } else if (data[0] === "<style") {
4782
- context.contentModel = ContentModel.STYLE;
4783
- } else if (data[0] === "<textarea") {
4784
- context.contentModel = ContentModel.TEXTAREA;
4785
- } else if (data[0] === "<title") {
4786
- context.contentModel = ContentModel.TITLE;
4787
- } else {
4788
- context.contentModel = ContentModel.TEXT;
4799
+ switch (data[0]) {
4800
+ case "<script": {
4801
+ context.contentModel = ContentModel.SCRIPT;
4802
+ break;
4803
+ }
4804
+ case "<style": {
4805
+ context.contentModel = ContentModel.STYLE;
4806
+ break;
4807
+ }
4808
+ case "<textarea": {
4809
+ context.contentModel = ContentModel.TEXTAREA;
4810
+ break;
4811
+ }
4812
+ case "<title": {
4813
+ context.contentModel = ContentModel.TITLE;
4814
+ break;
4815
+ }
4816
+ default: {
4817
+ context.contentModel = ContentModel.TEXT;
4818
+ }
4789
4819
  }
4790
4820
  }
4791
4821
  }
@@ -4958,7 +4988,7 @@ class AttrDelimiter extends Rule {
4958
4988
  }
4959
4989
 
4960
4990
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4961
- const defaults$w = {
4991
+ const defaults$x = {
4962
4992
  pattern: DEFAULT_PATTERN,
4963
4993
  ignoreForeign: true
4964
4994
  };
@@ -4991,7 +5021,7 @@ function generateDescription(name, pattern) {
4991
5021
  class AttrPattern extends Rule {
4992
5022
  pattern;
4993
5023
  constructor(options) {
4994
- super({ ...defaults$w, ...options });
5024
+ super({ ...defaults$x, ...options });
4995
5025
  this.pattern = generateRegexp(this.options.pattern);
4996
5026
  }
4997
5027
  static schema() {
@@ -5038,7 +5068,7 @@ class AttrPattern extends Rule {
5038
5068
  }
5039
5069
  }
5040
5070
 
5041
- const defaults$v = {
5071
+ const defaults$w = {
5042
5072
  style: "auto",
5043
5073
  unquoted: false
5044
5074
  };
@@ -5104,7 +5134,7 @@ class AttrQuotes extends Rule {
5104
5134
  };
5105
5135
  }
5106
5136
  constructor(options) {
5107
- super({ ...defaults$v, ...options });
5137
+ super({ ...defaults$w, ...options });
5108
5138
  this.style = parseStyle$3(this.options.style);
5109
5139
  }
5110
5140
  setup() {
@@ -5186,10 +5216,10 @@ class AttrSpacing extends Rule {
5186
5216
 
5187
5217
  function pick(attr) {
5188
5218
  const result = {};
5189
- if (typeof attr.enum !== "undefined") {
5219
+ if (attr.enum !== void 0) {
5190
5220
  result.enum = attr.enum;
5191
5221
  }
5192
- if (typeof attr.boolean !== "undefined") {
5222
+ if (attr.boolean !== void 0) {
5193
5223
  result.boolean = attr.boolean;
5194
5224
  }
5195
5225
  return result;
@@ -5260,13 +5290,13 @@ class AttributeAllowedValues extends Rule {
5260
5290
  }
5261
5291
  }
5262
5292
 
5263
- const defaults$u = {
5293
+ const defaults$v = {
5264
5294
  style: "omit"
5265
5295
  };
5266
5296
  class AttributeBooleanStyle extends Rule {
5267
5297
  hasInvalidStyle;
5268
5298
  constructor(options) {
5269
- super({ ...defaults$u, ...options });
5299
+ super({ ...defaults$v, ...options });
5270
5300
  this.hasInvalidStyle = parseStyle$2(this.options.style);
5271
5301
  }
5272
5302
  static schema() {
@@ -5336,13 +5366,13 @@ function reportMessage$1(attr, style) {
5336
5366
  return "";
5337
5367
  }
5338
5368
 
5339
- const defaults$t = {
5369
+ const defaults$u = {
5340
5370
  style: "omit"
5341
5371
  };
5342
5372
  class AttributeEmptyStyle extends Rule {
5343
5373
  hasInvalidStyle;
5344
5374
  constructor(options) {
5345
- super({ ...defaults$t, ...options });
5375
+ super({ ...defaults$u, ...options });
5346
5376
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5347
5377
  }
5348
5378
  static schema() {
@@ -5459,7 +5489,7 @@ class AttributeMisuse extends Rule {
5459
5489
  }
5460
5490
  }
5461
5491
 
5462
- const defaults$s = {
5492
+ const defaults$t = {
5463
5493
  preferred: void 0
5464
5494
  };
5465
5495
  function isPasswordInput(event) {
@@ -5476,7 +5506,7 @@ function isGroupingToken(token) {
5476
5506
  class AutocompletePassword extends Rule {
5477
5507
  preferred;
5478
5508
  constructor(options) {
5479
- super({ ...defaults$s, ...options });
5509
+ super({ ...defaults$t, ...options });
5480
5510
  this.preferred = options.preferred?.toLowerCase();
5481
5511
  }
5482
5512
  static schema() {
@@ -5706,7 +5736,7 @@ class BasePatternRule extends Rule {
5706
5736
  }
5707
5737
  }
5708
5738
 
5709
- const defaults$r = {
5739
+ const defaults$s = {
5710
5740
  pattern: "kebabcase"
5711
5741
  };
5712
5742
  class ClassPattern extends BasePatternRule {
@@ -5714,7 +5744,7 @@ class ClassPattern extends BasePatternRule {
5714
5744
  super({
5715
5745
  ruleId: "class-pattern",
5716
5746
  attr: "class",
5717
- options: { ...defaults$r, ...options },
5747
+ options: { ...defaults$s, ...options },
5718
5748
  allowedPatterns: patternNames
5719
5749
  // allow all patterns
5720
5750
  });
@@ -5864,13 +5894,13 @@ class CloseOrder extends Rule {
5864
5894
  }
5865
5895
  }
5866
5896
 
5867
- const defaults$q = {
5897
+ const defaults$r = {
5868
5898
  include: null,
5869
5899
  exclude: null
5870
5900
  };
5871
5901
  class Deprecated extends Rule {
5872
5902
  constructor(options) {
5873
- super({ ...defaults$q, ...options });
5903
+ super({ ...defaults$r, ...options });
5874
5904
  }
5875
5905
  static schema() {
5876
5906
  return {
@@ -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;
@@ -5978,6 +6008,137 @@ function prettySource(source) {
5978
6008
  }
5979
6009
  }
5980
6010
 
6011
+ function quote(value, char = '"') {
6012
+ return `${char}${value}${char}`;
6013
+ }
6014
+
6015
+ const defaults$q = {
6016
+ classes: []
6017
+ };
6018
+ function isRelevant$6(event) {
6019
+ return event.key.toLowerCase() === "class";
6020
+ }
6021
+ function normalizeEntry(entry) {
6022
+ const { class: className, message, replacement = [], url } = entry;
6023
+ return {
6024
+ class: className,
6025
+ message,
6026
+ replacement: Array.isArray(replacement) ? replacement : [replacement],
6027
+ url
6028
+ };
6029
+ }
6030
+ function formatDeprecatedMessage(className, entry) {
6031
+ let message = `class "${className}" is deprecated`;
6032
+ if (entry.replacement.length > 0) {
6033
+ const joined = utils_naturalJoin.naturalJoin(
6034
+ entry.replacement.map((r) => quote(r)),
6035
+ "or"
6036
+ );
6037
+ message += ` and replaced with ${joined}`;
6038
+ }
6039
+ if (entry.message) {
6040
+ message += `: ${entry.message}`;
6041
+ }
6042
+ return message;
6043
+ }
6044
+ function formatDocumentationDescription(context) {
6045
+ const text = [];
6046
+ const className = context.class;
6047
+ let description = `The class \`${className}\` is deprecated and should not be used`;
6048
+ if (context.message) {
6049
+ description += `: ${context.message}.`;
6050
+ } else {
6051
+ description += ".";
6052
+ }
6053
+ text.push(description);
6054
+ if (context.replacement.length === 1) {
6055
+ text.push(`Use the replacement class ${quote(context.replacement[0], "`")} instead.`);
6056
+ } else if (context.replacement.length > 1) {
6057
+ const listItems = context.replacement.map((r) => `- ${quote(r, "`")}`);
6058
+ text.push(`Use one of the following replacement classes instead:
6059
+ ${listItems.join("\n")}`);
6060
+ }
6061
+ if (context.url) {
6062
+ text.push(`For details see: ${context.url}`);
6063
+ }
6064
+ return text.join("\n\n");
6065
+ }
6066
+ class DeprecatedClass extends Rule {
6067
+ deprecatedMap;
6068
+ constructor(options) {
6069
+ super({ ...defaults$q, ...options });
6070
+ const { classes } = this.options;
6071
+ this.deprecatedMap = new Map(classes.map((entry) => [entry.class, normalizeEntry(entry)]));
6072
+ }
6073
+ static schema() {
6074
+ return {
6075
+ classes: {
6076
+ type: "array",
6077
+ items: {
6078
+ type: "object",
6079
+ properties: {
6080
+ class: {
6081
+ type: "string"
6082
+ },
6083
+ message: {
6084
+ type: "string"
6085
+ },
6086
+ replacement: {
6087
+ anyOf: [
6088
+ {
6089
+ type: "string"
6090
+ },
6091
+ {
6092
+ type: "array",
6093
+ items: {
6094
+ type: "string"
6095
+ }
6096
+ }
6097
+ ]
6098
+ },
6099
+ url: {
6100
+ type: "string"
6101
+ }
6102
+ },
6103
+ required: ["class"],
6104
+ additionalProperties: false
6105
+ }
6106
+ }
6107
+ };
6108
+ }
6109
+ documentation(context) {
6110
+ return {
6111
+ description: formatDocumentationDescription(context),
6112
+ url: "https://html-validate.org/rules/deprecated-class.html"
6113
+ };
6114
+ }
6115
+ setup() {
6116
+ this.on("attr", isRelevant$6, (event) => {
6117
+ const { value, valueLocation, target } = event;
6118
+ const classes = new DOMTokenList(value, valueLocation);
6119
+ for (const { item, location } of classes.iterator()) {
6120
+ const deprecatedEntry = this.deprecatedMap.get(item);
6121
+ if (!deprecatedEntry) {
6122
+ continue;
6123
+ }
6124
+ const message = formatDeprecatedMessage(item, deprecatedEntry);
6125
+ const context = {
6126
+ class: item,
6127
+ message: deprecatedEntry.message ?? null,
6128
+ replacement: deprecatedEntry.replacement,
6129
+ url: deprecatedEntry.url ?? null
6130
+ };
6131
+ this.report({
6132
+ node: target,
6133
+ message,
6134
+ location,
6135
+ context
6136
+ });
6137
+ }
6138
+ });
6139
+ }
6140
+ }
6141
+
5981
6142
  class DeprecatedRule extends Rule {
5982
6143
  documentation(context) {
5983
6144
  const preamble = context ? `The rule "${context}"` : "This rule";
@@ -6104,7 +6265,7 @@ class ElementCase extends Rule {
6104
6265
  });
6105
6266
  }
6106
6267
  validateCase(target, targetLocation) {
6107
- const letters = target.tagName.replace(/[^a-z]+/gi, "");
6268
+ const letters = target.tagName.replaceAll(/[^a-z]+/gi, "");
6108
6269
  if (!this.style.match(letters)) {
6109
6270
  const location = sliceLocation(targetLocation, 1);
6110
6271
  this.report(target, `Element "${target.tagName}" should be ${this.style.name}`, location);
@@ -6708,8 +6869,8 @@ function haveName(name) {
6708
6869
  return typeof name === "string" && name !== "";
6709
6870
  }
6710
6871
  function allowSharedName(node, shared) {
6711
- const type = node.getAttribute("type");
6712
- return Boolean(type?.valueMatches(shared, false));
6872
+ const type = getControlType(node);
6873
+ return shared.includes(type);
6713
6874
  }
6714
6875
  function isInputHidden(element) {
6715
6876
  return element.is("input") && element.getAttributeValue("type") === "hidden";
@@ -6717,6 +6878,13 @@ function isInputHidden(element) {
6717
6878
  function isInputCheckbox(element) {
6718
6879
  return element.is("input") && element.getAttributeValue("type") === "checkbox";
6719
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
+ }
6720
6888
  function isCheckboxWithDefault(control, previous, options) {
6721
6889
  const { allowCheckboxDefault } = options;
6722
6890
  if (!allowCheckboxDefault) {
@@ -6854,7 +7022,7 @@ class FormDupName extends Rule {
6854
7022
  validateSharedName(control, group, attr, name) {
6855
7023
  const uniqueElements = this.getUniqueElements(group);
6856
7024
  const sharedElements = this.getSharedElements(group);
6857
- const type = control.getAttributeValue("type") ?? "";
7025
+ const type = getControlType(control);
6858
7026
  if (uniqueElements.has(name) || sharedElements.has(name) && sharedElements.get(name) !== type) {
6859
7027
  const context = {
6860
7028
  name,
@@ -6916,7 +7084,7 @@ function isRelevant$5(event) {
6916
7084
  function extractLevel(node) {
6917
7085
  const match = /^[hH](\d)$/.exec(node.tagName);
6918
7086
  if (match) {
6919
- return parseInt(match[1], 10);
7087
+ return Number.parseInt(match[1], 10);
6920
7088
  } else {
6921
7089
  return null;
6922
7090
  }
@@ -6929,7 +7097,7 @@ function parseMaxInitial(value) {
6929
7097
  if (!match) {
6930
7098
  return 1;
6931
7099
  }
6932
- return parseInt(match[1], 10);
7100
+ return Number.parseInt(match[1], 10);
6933
7101
  }
6934
7102
  class HeadingLevel extends Rule {
6935
7103
  minInitialRank;
@@ -7078,10 +7246,10 @@ class HeadingLevel extends Rule {
7078
7246
  this.stack.pop();
7079
7247
  }
7080
7248
  getPrevRoot() {
7081
- return this.stack[this.stack.length - 2];
7249
+ return this.stack.at(-2);
7082
7250
  }
7083
7251
  getCurrentRoot() {
7084
- return this.stack[this.stack.length - 1];
7252
+ return this.stack.at(-1);
7085
7253
  }
7086
7254
  isSectioningRoot(node) {
7087
7255
  const context = {
@@ -7678,7 +7846,7 @@ function parseContent(text) {
7678
7846
  const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7679
7847
  if (match) {
7680
7848
  return {
7681
- delay: parseInt(match[1], 10),
7849
+ delay: Number.parseInt(match[1], 10),
7682
7850
  url: match[2]
7683
7851
  };
7684
7852
  } else {
@@ -8002,13 +8170,12 @@ class NoDupClass extends Rule {
8002
8170
  }
8003
8171
  const classes = new DOMTokenList(event.value, event.valueLocation);
8004
8172
  const unique = /* @__PURE__ */ new Set();
8005
- classes.forEach((cur, index) => {
8006
- if (unique.has(cur)) {
8007
- const location = classes.location(index);
8008
- 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);
8009
8176
  }
8010
- unique.add(cur);
8011
- });
8177
+ unique.add(item);
8178
+ }
8012
8179
  });
8013
8180
  }
8014
8181
  }
@@ -8740,7 +8907,7 @@ class NoUnusedDisable extends Rule {
8740
8907
  setup() {
8741
8908
  }
8742
8909
  reportUnused(unused, options, location) {
8743
- const tokens = new DOMTokenList(options.replace(/,/g, " "), location);
8910
+ const tokens = new DOMTokenList(options.replaceAll(",", " "), location);
8744
8911
  for (const ruleId of unused) {
8745
8912
  const index = tokens.indexOf(ruleId);
8746
8913
  const tokenLocation = index >= 0 ? tokens.location(index) : location;
@@ -9061,19 +9228,19 @@ const supportSri = {
9061
9228
  link: "href",
9062
9229
  script: "src"
9063
9230
  };
9064
- const supportedRel = ["stylesheet", "preload", "modulepreload"];
9065
- const supportedPreload = ["style", "script"];
9231
+ const supportedRel = /* @__PURE__ */ new Set(["stylesheet", "preload", "modulepreload"]);
9232
+ const supportedPreload = /* @__PURE__ */ new Set(["style", "script"]);
9066
9233
  function linkSupportsSri(node) {
9067
9234
  const rel = node.getAttribute("rel");
9068
9235
  if (typeof rel?.value !== "string") {
9069
9236
  return false;
9070
9237
  }
9071
- if (!supportedRel.includes(rel.value)) {
9238
+ if (!supportedRel.has(rel.value)) {
9072
9239
  return false;
9073
9240
  }
9074
9241
  if (rel.value === "preload") {
9075
9242
  const as = node.getAttribute("as");
9076
- return typeof as?.value === "string" && supportedPreload.includes(as.value);
9243
+ return typeof as?.value === "string" && supportedPreload.has(as.value);
9077
9244
  }
9078
9245
  return true;
9079
9246
  }
@@ -9190,13 +9357,13 @@ class ScriptElement extends Rule {
9190
9357
  }
9191
9358
  }
9192
9359
 
9193
- const javascript = [
9360
+ const javascript = /* @__PURE__ */ new Set([
9194
9361
  "",
9195
9362
  "application/ecmascript",
9196
9363
  "application/javascript",
9197
9364
  "text/ecmascript",
9198
9365
  "text/javascript"
9199
- ];
9366
+ ]);
9200
9367
  class ScriptType extends Rule {
9201
9368
  documentation() {
9202
9369
  return {
@@ -9227,7 +9394,7 @@ class ScriptType extends Rule {
9227
9394
  }
9228
9395
  isJavascript(mime) {
9229
9396
  const type = mime.replace(/;.*/, "");
9230
- return javascript.includes(type);
9397
+ return javascript.has(type);
9231
9398
  }
9232
9399
  }
9233
9400
 
@@ -9803,7 +9970,7 @@ class UnknownCharReference extends Rule {
9803
9970
  }
9804
9971
 
9805
9972
  const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
9806
- const fieldNames1 = [
9973
+ const fieldNames1 = /* @__PURE__ */ new Set([
9807
9974
  "name",
9808
9975
  "honorific-prefix",
9809
9976
  "given-name",
@@ -9848,8 +10015,8 @@ const fieldNames1 = [
9848
10015
  "sex",
9849
10016
  "url",
9850
10017
  "photo"
9851
- ];
9852
- const fieldNames2 = [
10018
+ ]);
10019
+ const fieldNames2 = /* @__PURE__ */ new Set([
9853
10020
  "tel",
9854
10021
  "tel-country-code",
9855
10022
  "tel-national",
@@ -9860,7 +10027,7 @@ const fieldNames2 = [
9860
10027
  "tel-extension",
9861
10028
  "email",
9862
10029
  "impp"
9863
- ];
10030
+ ]);
9864
10031
  const fieldNameGroup = {
9865
10032
  name: "text",
9866
10033
  "honorific-prefix": "text",
@@ -9925,14 +10092,14 @@ function matchHint(token) {
9925
10092
  return token === "shipping" || token === "billing";
9926
10093
  }
9927
10094
  function matchFieldNames1(token) {
9928
- return fieldNames1.includes(token);
10095
+ return fieldNames1.has(token);
9929
10096
  }
9930
10097
  function matchContact(token) {
9931
10098
  const haystack = ["home", "work", "mobile", "fax", "pager"];
9932
10099
  return haystack.includes(token);
9933
10100
  }
9934
10101
  function matchFieldNames2(token) {
9935
- return fieldNames2.includes(token);
10102
+ return fieldNames2.has(token);
9936
10103
  }
9937
10104
  function matchWebauthn(token) {
9938
10105
  return token === "webauthn";
@@ -10603,11 +10770,13 @@ class H32 extends Rule {
10603
10770
  setup() {
10604
10771
  const formTags = this.getTagsWithProperty("form");
10605
10772
  const formSelector = formTags.join(",");
10773
+ const submitButtonTags = this.getTagsWithProperty("submitButton");
10774
+ const submitButtonSelector = submitButtonTags.join(",");
10606
10775
  this.on("dom:ready", (event) => {
10607
10776
  const { document } = event;
10608
10777
  const forms = document.querySelectorAll(formSelector);
10609
10778
  for (const form of forms) {
10610
- if (hasNestedSubmit(form)) {
10779
+ if (hasNestedSubmit(form, submitButtonSelector)) {
10611
10780
  continue;
10612
10781
  }
10613
10782
  if (hasAssociatedSubmit(document, form)) {
@@ -10619,15 +10788,15 @@ class H32 extends Rule {
10619
10788
  }
10620
10789
  }
10621
10790
  function isSubmit(node) {
10622
- const type = node.getAttribute("type");
10623
- return !type || type.valueMatches(/submit|image/);
10791
+ const meta = node.meta;
10792
+ return Boolean(meta?.submitButton);
10624
10793
  }
10625
10794
  function isAssociated(id, node) {
10626
10795
  const form = node.getAttribute("form");
10627
10796
  return Boolean(form?.valueMatches(id, true));
10628
10797
  }
10629
- function hasNestedSubmit(form) {
10630
- 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"));
10631
10800
  return matches.length > 0;
10632
10801
  }
10633
10802
  function hasAssociatedSubmit(document, form) {
@@ -10918,6 +11087,7 @@ const bundledRules = {
10918
11087
  "close-attr": CloseAttr,
10919
11088
  "close-order": CloseOrder,
10920
11089
  deprecated: Deprecated,
11090
+ "deprecated-class": DeprecatedClass,
10921
11091
  "deprecated-rule": DeprecatedRule,
10922
11092
  "doctype-html": NoStyleTag$1,
10923
11093
  "doctype-style": DoctypeStyle,
@@ -11175,11 +11345,11 @@ function dumpTree(root) {
11175
11345
  } else {
11176
11346
  lines.push("(root)");
11177
11347
  }
11178
- node.childElements.forEach((child, index) => {
11348
+ for (const [index, child] of node.childElements.entries()) {
11179
11349
  const s = lastSibling ? " " : "\u2502";
11180
11350
  const i = level > 0 ? `${indent}${s} ` : "";
11181
11351
  writeNode(child, level + 1, i, index);
11182
- });
11352
+ }
11183
11353
  }
11184
11354
  writeNode(root, 0, "", 0);
11185
11355
  return lines;
@@ -12294,7 +12464,7 @@ class EventHandler {
12294
12464
  }
12295
12465
 
12296
12466
  const name = "html-validate";
12297
- const version = "10.8.0";
12467
+ const version = "10.10.0";
12298
12468
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12299
12469
 
12300
12470
  function freeze(src) {
@@ -12310,7 +12480,13 @@ function isThenableArray(value) {
12310
12480
  return isThenable(value[0]);
12311
12481
  }
12312
12482
  class Reporter {
12483
+ /**
12484
+ * @internal
12485
+ */
12313
12486
  result;
12487
+ /**
12488
+ * @internal
12489
+ */
12314
12490
  constructor() {
12315
12491
  this.result = {};
12316
12492
  }
@@ -12323,16 +12499,16 @@ class Reporter {
12323
12499
  }
12324
12500
  const valid = reports.every((report) => report.valid);
12325
12501
  const merged = {};
12326
- reports.forEach((report) => {
12327
- report.results.forEach((result) => {
12502
+ for (const report of reports) {
12503
+ for (const result of report.results) {
12328
12504
  const key = result.filePath;
12329
12505
  if (key in merged) {
12330
12506
  merged[key].messages = [...merged[key].messages, ...result.messages];
12331
12507
  } else {
12332
12508
  merged[key] = { ...result };
12333
12509
  }
12334
- });
12335
- });
12510
+ }
12511
+ }
12336
12512
  const results = Object.values(merged).map((result) => {
12337
12513
  result.errorCount = countErrors(result.messages);
12338
12514
  result.warningCount = countWarnings(result.messages);
@@ -12345,8 +12521,11 @@ class Reporter {
12345
12521
  warningCount: sumWarnings(results)
12346
12522
  };
12347
12523
  }
12348
- /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12349
- add(rule, message, severity, node, location, context) {
12524
+ /**
12525
+ * @internal
12526
+ */
12527
+ add(options) {
12528
+ const { rule, message, severity, node, location, context } = options;
12350
12529
  if (!(location.filename in this.result)) {
12351
12530
  this.result[location.filename] = [];
12352
12531
  }
@@ -12371,17 +12550,23 @@ class Reporter {
12371
12550
  }
12372
12551
  this.result[location.filename].push(entry);
12373
12552
  }
12553
+ /**
12554
+ * @internal
12555
+ */
12374
12556
  addManual(filename, message) {
12375
12557
  if (!(filename in this.result)) {
12376
12558
  this.result[filename] = [];
12377
12559
  }
12378
12560
  this.result[filename].push(message);
12379
12561
  }
12562
+ /**
12563
+ * @internal
12564
+ */
12380
12565
  save(sources) {
12381
12566
  const report = {
12382
12567
  valid: this.isValid(),
12383
12568
  results: Object.keys(this.result).map((filePath) => {
12384
- const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
12569
+ const messages = Array.from(this.result[filePath], freeze).toSorted(messageSort);
12385
12570
  const source = (sources ?? []).find((source2) => filePath === source2.filename);
12386
12571
  return {
12387
12572
  filePath,
@@ -12398,6 +12583,9 @@ class Reporter {
12398
12583
  report.warningCount = sumWarnings(report.results);
12399
12584
  return report;
12400
12585
  }
12586
+ /**
12587
+ * @internal
12588
+ */
12401
12589
  isValid() {
12402
12590
  const numErrors = Object.values(this.result).reduce((sum, messages) => {
12403
12591
  return sum + countErrors(messages);
@@ -12460,6 +12648,7 @@ class ParserError extends Error {
12460
12648
  location;
12461
12649
  constructor(location, message) {
12462
12650
  super(message);
12651
+ this.name = "ParserError";
12463
12652
  this.location = location;
12464
12653
  }
12465
12654
  }
@@ -12616,7 +12805,7 @@ class Parser {
12616
12805
  const tokens = Array.from(
12617
12806
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, startToken.location)
12618
12807
  );
12619
- const endToken = tokens.slice(-1)[0];
12808
+ const endToken = tokens.at(-1);
12620
12809
  const closeOptional = this.closeOptional(startToken);
12621
12810
  const parent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
12622
12811
  const node = HtmlElement.fromTokens(
@@ -12734,7 +12923,7 @@ class Parser {
12734
12923
  const endTokens = Array.from(
12735
12924
  this.consumeUntil(tokenStream, TokenType.TAG_CLOSE, last.location)
12736
12925
  );
12737
- endToken = endTokens.slice(-1)[0];
12926
+ endToken = endTokens.at(-1);
12738
12927
  const selfClosed = endToken.data[0] === "/>";
12739
12928
  if (tagClosed) {
12740
12929
  startToken = last;
@@ -13016,7 +13205,7 @@ class Parser {
13016
13205
  this.event.once("*", cb);
13017
13206
  }
13018
13207
  trigger(event, data) {
13019
- if (typeof data.location === "undefined") {
13208
+ if (data.location === void 0) {
13020
13209
  throw new Error("Triggered event must contain location");
13021
13210
  }
13022
13211
  this.event.trigger(event, data);
@@ -13144,7 +13333,9 @@ class Engine {
13144
13333
  }
13145
13334
  lines.push({ event, data });
13146
13335
  });
13147
- source.forEach((src) => parser.parseHtml(src));
13336
+ for (const src of source) {
13337
+ parser.parseHtml(src);
13338
+ }
13148
13339
  return lines;
13149
13340
  }
13150
13341
  dumpTokens(source) {
@@ -13609,7 +13800,7 @@ const entities = {
13609
13800
  "&": "&amp;"
13610
13801
  };
13611
13802
  function xmlescape(src) {
13612
- return src.toString().replace(/[><'"&]/g, (match) => {
13803
+ return src.toString().replaceAll(/[><'"&]/g, (match) => {
13613
13804
  return entities[match];
13614
13805
  });
13615
13806
  }
@@ -13629,11 +13820,11 @@ function checkstyleFormatter(results) {
13629
13820
  `;
13630
13821
  output += `<checkstyle version="4.3">
13631
13822
  `;
13632
- results.forEach((result) => {
13823
+ for (const result of results) {
13633
13824
  const messages = result.messages;
13634
13825
  output += ` <file name="${xmlescape(result.filePath)}">
13635
13826
  `;
13636
- messages.forEach((message) => {
13827
+ for (const message of messages) {
13637
13828
  const ruleId = xmlescape(`htmlvalidate.rules.${message.ruleId}`);
13638
13829
  output += " ";
13639
13830
  output += [
@@ -13644,9 +13835,9 @@ function checkstyleFormatter(results) {
13644
13835
  `source="${ruleId}" />`
13645
13836
  ].join(" ");
13646
13837
  output += "\n";
13647
- });
13838
+ }
13648
13839
  output += " </file>\n";
13649
- });
13840
+ }
13650
13841
  output += "</checkstyle>\n";
13651
13842
  return output;
13652
13843
  }
@@ -13716,11 +13907,11 @@ function codeFrameColumns(rawLines, loc) {
13716
13907
  if (hasMarker) {
13717
13908
  let markerLine = "";
13718
13909
  if (Array.isArray(hasMarker)) {
13719
- 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, " ");
13720
13911
  const numberOfMarkers = hasMarker[1] || 1;
13721
13912
  markerLine = [
13722
13913
  "\n ",
13723
- gutter.replace(/\d/g, " "),
13914
+ gutter.replaceAll(/\d/g, " "),
13724
13915
  " ",
13725
13916
  markerSpacing,
13726
13917
  "^".repeat(numberOfMarkers)
@@ -13858,10 +14049,10 @@ function stylish(results) {
13858
14049
  function textFormatter(results) {
13859
14050
  let output = "";
13860
14051
  let total = 0;
13861
- results.forEach((result) => {
14052
+ for (const result of results) {
13862
14053
  const messages = result.messages;
13863
14054
  if (messages.length === 0) {
13864
- return;
14055
+ continue;
13865
14056
  }
13866
14057
  total += messages.length;
13867
14058
  output += messages.map((message) => {
@@ -13877,7 +14068,7 @@ function textFormatter(results) {
13877
14068
  return `${location}: ${messageType} [${message.ruleId}] ${message.message}
13878
14069
  `;
13879
14070
  }).join("");
13880
- });
14071
+ }
13881
14072
  return total > 0 ? output : "";
13882
14073
  }
13883
14074
  const formatter = textFormatter;
@@ -14709,7 +14900,7 @@ var ignoreExports = /*@__PURE__*/ requireIgnore();
14709
14900
  var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14710
14901
 
14711
14902
  const engines = {
14712
- node: "^20.19.0 || >= 22.12.0"
14903
+ node: "^20.19.0 || >= 22.16.0"
14713
14904
  };
14714
14905
 
14715
14906
  var workerPath = "./jest-worker.js";