html-validate 10.1.1 → 10.2.1

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 (58) hide show
  1. package/dist/cjs/cli.js +201 -203
  2. package/dist/cjs/cli.js.map +1 -1
  3. package/dist/cjs/core-browser.js.map +1 -1
  4. package/dist/cjs/core-nodejs.js +3 -3
  5. package/dist/cjs/core-nodejs.js.map +1 -1
  6. package/dist/cjs/core.js +431 -368
  7. package/dist/cjs/core.js.map +1 -1
  8. package/dist/cjs/elements.js +2 -2
  9. package/dist/cjs/elements.js.map +1 -1
  10. package/dist/cjs/html-validate.js +2 -3
  11. package/dist/cjs/html-validate.js.map +1 -1
  12. package/dist/cjs/index.js +0 -1
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/jest-diff.js +2 -0
  15. package/dist/cjs/jest-diff.js.map +1 -1
  16. package/dist/cjs/jest-worker.js.map +1 -1
  17. package/dist/cjs/jest.js +1 -1
  18. package/dist/cjs/jest.js.map +1 -1
  19. package/dist/cjs/matchers-jestonly.js +1 -1
  20. package/dist/cjs/matchers-jestonly.js.map +1 -1
  21. package/dist/cjs/matchers.js.map +1 -1
  22. package/dist/cjs/meta-helper.js +4 -4
  23. package/dist/cjs/meta-helper.js.map +1 -1
  24. package/dist/cjs/test-utils.js +2 -2
  25. package/dist/cjs/test-utils.js.map +2 -2
  26. package/dist/cjs/tsdoc-metadata.json +1 -1
  27. package/dist/cjs/vitest.js.map +1 -1
  28. package/dist/esm/browser.js +1 -1
  29. package/dist/esm/cli.js +201 -202
  30. package/dist/esm/cli.js.map +1 -1
  31. package/dist/esm/core-browser.js.map +1 -1
  32. package/dist/esm/core-nodejs.js +3 -3
  33. package/dist/esm/core-nodejs.js.map +1 -1
  34. package/dist/esm/core.js +432 -369
  35. package/dist/esm/core.js.map +1 -1
  36. package/dist/esm/elements.js +3 -3
  37. package/dist/esm/elements.js.map +1 -1
  38. package/dist/esm/html-validate.js +2 -3
  39. package/dist/esm/html-validate.js.map +1 -1
  40. package/dist/esm/index.js +1 -2
  41. package/dist/esm/index.js.map +1 -1
  42. package/dist/esm/jest-diff.js +2 -0
  43. package/dist/esm/jest-diff.js.map +1 -1
  44. package/dist/esm/jest-worker.js.map +1 -1
  45. package/dist/esm/jest.js +1 -1
  46. package/dist/esm/jest.js.map +1 -1
  47. package/dist/esm/matchers-jestonly.js +1 -1
  48. package/dist/esm/matchers-jestonly.js.map +1 -1
  49. package/dist/esm/matchers.js.map +1 -1
  50. package/dist/esm/meta-helper.js +4 -4
  51. package/dist/esm/meta-helper.js.map +1 -1
  52. package/dist/esm/test-utils.js +1 -1
  53. package/dist/esm/test-utils.js.map +1 -1
  54. package/dist/esm/vitest.js.map +1 -1
  55. package/dist/tsdoc-metadata.json +1 -1
  56. package/dist/types/browser.d.ts +1 -1
  57. package/dist/types/index.d.ts +1 -1
  58. package/package.json +2 -2
package/dist/cjs/core.js CHANGED
@@ -505,27 +505,6 @@ class SchemaValidationError extends UserError {
505
505
  }
506
506
  }
507
507
 
508
- function cyrb53(str) {
509
- const a = 2654435761;
510
- const b = 1597334677;
511
- const c = 2246822507;
512
- const d = 3266489909;
513
- const e = 4294967296;
514
- const f = 2097151;
515
- const seed = 0;
516
- let h1 = 3735928559 ^ seed;
517
- let h2 = 1103547991 ^ seed;
518
- for (let i = 0, ch; i < str.length; i++) {
519
- ch = str.charCodeAt(i);
520
- h1 = Math.imul(h1 ^ ch, a);
521
- h2 = Math.imul(h2 ^ ch, b);
522
- }
523
- h1 = Math.imul(h1 ^ h1 >>> 16, c) ^ Math.imul(h2 ^ h2 >>> 13, d);
524
- h2 = Math.imul(h2 ^ h2 >>> 16, c) ^ Math.imul(h1 ^ h1 >>> 13, d);
525
- return e * (f & h2) + (h1 >>> 0);
526
- }
527
- const computeHash = cyrb53;
528
-
529
508
  const $schema$1 = "http://json-schema.org/draft-06/schema#";
530
509
  const $id$1 = "https://html-validate.org/schemas/elements.json";
531
510
  const type$1 = "object";
@@ -1001,6 +980,27 @@ const ajvFunctionKeyword = {
1001
980
  validate: ajvFunctionValidate
1002
981
  };
1003
982
 
983
+ function cyrb53(str) {
984
+ const a = 2654435761;
985
+ const b = 1597334677;
986
+ const c = 2246822507;
987
+ const d = 3266489909;
988
+ const e = 4294967296;
989
+ const f = 2097151;
990
+ const seed = 0;
991
+ let h1 = 3735928559 ^ seed;
992
+ let h2 = 1103547991 ^ seed;
993
+ for (let i = 0, ch; i < str.length; i++) {
994
+ ch = str.charCodeAt(i);
995
+ h1 = Math.imul(h1 ^ ch, a);
996
+ h2 = Math.imul(h2 ^ ch, b);
997
+ }
998
+ h1 = Math.imul(h1 ^ h1 >>> 16, c) ^ Math.imul(h2 ^ h2 >>> 13, d);
999
+ h2 = Math.imul(h2 ^ h2 >>> 16, c) ^ Math.imul(h1 ^ h1 >>> 13, d);
1000
+ return e * (f & h2) + (h1 >>> 0);
1001
+ }
1002
+ const computeHash = cyrb53;
1003
+
1004
1004
  var TextContent$1 = /* @__PURE__ */ ((TextContent2) => {
1005
1005
  TextContent2["NONE"] = "none";
1006
1006
  TextContent2["DEFAULT"] = "default";
@@ -1495,7 +1495,7 @@ function sliceLocation(location, begin, end, wrap) {
1495
1495
  if (wrap) {
1496
1496
  let index = -1;
1497
1497
  const col = sliced.column;
1498
- do {
1498
+ for (; ; ) {
1499
1499
  index = wrap.indexOf("\n", index + 1);
1500
1500
  if (index >= 0 && index < begin) {
1501
1501
  sliced.column = col - (index + 1);
@@ -1503,7 +1503,7 @@ function sliceLocation(location, begin, end, wrap) {
1503
1503
  } else {
1504
1504
  break;
1505
1505
  }
1506
- } while (true);
1506
+ }
1507
1507
  }
1508
1508
  return sliced;
1509
1509
  }
@@ -1601,7 +1601,6 @@ class DOMNode {
1601
1601
  * @internal
1602
1602
  */
1603
1603
  unique;
1604
- /* eslint-disable-next-line sonarjs/use-type-alias -- technical debt */
1605
1604
  cache;
1606
1605
  /**
1607
1606
  * Set of disabled rules for this node.
@@ -1641,8 +1640,8 @@ class DOMNode {
1641
1640
  *
1642
1641
  * @internal
1643
1642
  */
1644
- cacheEnable() {
1645
- this.cache = /* @__PURE__ */ new Map();
1643
+ cacheEnable(enable = true) {
1644
+ this.cache = enable ? /* @__PURE__ */ new Map() : null;
1646
1645
  }
1647
1646
  cacheGet(key) {
1648
1647
  if (this.cache) {
@@ -3329,172 +3328,6 @@ function parseQuantifier(quantifier) {
3329
3328
  }
3330
3329
  }
3331
3330
 
3332
- const $schema = "http://json-schema.org/draft-06/schema#";
3333
- const $id = "https://html-validate.org/schemas/config.json";
3334
- const type = "object";
3335
- const additionalProperties = false;
3336
- const properties = {
3337
- $schema: {
3338
- type: "string"
3339
- },
3340
- root: {
3341
- type: "boolean",
3342
- title: "Mark as root configuration",
3343
- description: "If this is set to true no further configurations will be searched.",
3344
- "default": false
3345
- },
3346
- "extends": {
3347
- type: "array",
3348
- items: {
3349
- type: "string"
3350
- },
3351
- title: "Configurations to extend",
3352
- description: "Array of shareable or builtin configurations to extend."
3353
- },
3354
- elements: {
3355
- type: "array",
3356
- items: {
3357
- anyOf: [
3358
- {
3359
- type: "string"
3360
- },
3361
- {
3362
- type: "object"
3363
- }
3364
- ]
3365
- },
3366
- title: "Element metadata to load",
3367
- description: "Array of modules, plugins or files to load element metadata from. Use <rootDir> to refer to the folder with the package.json file.",
3368
- examples: [
3369
- [
3370
- "html-validate:recommended",
3371
- "plugin:recommended",
3372
- "module",
3373
- "./local-file.json"
3374
- ]
3375
- ]
3376
- },
3377
- plugins: {
3378
- type: "array",
3379
- items: {
3380
- anyOf: [
3381
- {
3382
- type: "string"
3383
- },
3384
- {
3385
- type: "object"
3386
- }
3387
- ]
3388
- },
3389
- title: "Plugins to load",
3390
- description: "Array of plugins load. Use <rootDir> to refer to the folder with the package.json file.",
3391
- examples: [
3392
- [
3393
- "my-plugin",
3394
- "./local-plugin"
3395
- ]
3396
- ]
3397
- },
3398
- transform: {
3399
- type: "object",
3400
- additionalProperties: {
3401
- anyOf: [
3402
- {
3403
- type: "string"
3404
- },
3405
- {
3406
- "function": true
3407
- }
3408
- ]
3409
- },
3410
- title: "File transformations to use.",
3411
- description: "Object where key is regular expression to match filename and value is name of transformer or a function.",
3412
- examples: [
3413
- {
3414
- "^.*\\.foo$": "my-transformer",
3415
- "^.*\\.bar$": "my-plugin",
3416
- "^.*\\.baz$": "my-plugin:named"
3417
- }
3418
- ]
3419
- },
3420
- rules: {
3421
- type: "object",
3422
- patternProperties: {
3423
- ".*": {
3424
- anyOf: [
3425
- {
3426
- "enum": [
3427
- 0,
3428
- 1,
3429
- 2,
3430
- "off",
3431
- "warn",
3432
- "error"
3433
- ]
3434
- },
3435
- {
3436
- type: "array",
3437
- minItems: 1,
3438
- maxItems: 1,
3439
- items: [
3440
- {
3441
- "enum": [
3442
- 0,
3443
- 1,
3444
- 2,
3445
- "off",
3446
- "warn",
3447
- "error"
3448
- ]
3449
- }
3450
- ]
3451
- },
3452
- {
3453
- type: "array",
3454
- minItems: 2,
3455
- maxItems: 2,
3456
- items: [
3457
- {
3458
- "enum": [
3459
- 0,
3460
- 1,
3461
- 2,
3462
- "off",
3463
- "warn",
3464
- "error"
3465
- ]
3466
- },
3467
- {
3468
- }
3469
- ]
3470
- }
3471
- ]
3472
- }
3473
- },
3474
- title: "Rule configuration.",
3475
- description: "Enable/disable rules, set severity. Some rules have additional configuration like style or patterns to use.",
3476
- examples: [
3477
- {
3478
- foo: "error",
3479
- bar: "off",
3480
- baz: [
3481
- "error",
3482
- {
3483
- style: "camelcase"
3484
- }
3485
- ]
3486
- }
3487
- ]
3488
- }
3489
- };
3490
- var configurationSchema = {
3491
- $schema: $schema,
3492
- $id: $id,
3493
- type: type,
3494
- additionalProperties: additionalProperties,
3495
- properties: properties
3496
- };
3497
-
3498
3331
  var Severity = /* @__PURE__ */ ((Severity2) => {
3499
3332
  Severity2[Severity2["DISABLED"] = 0] = "DISABLED";
3500
3333
  Severity2[Severity2["WARN"] = 1] = "WARN";
@@ -3517,35 +3350,6 @@ function parseSeverity(value) {
3517
3350
  }
3518
3351
  }
3519
3352
 
3520
- function escape(value) {
3521
- return JSON.stringify(value);
3522
- }
3523
- function format(value, quote = false) {
3524
- if (value === null || value === void 0) {
3525
- return "null";
3526
- }
3527
- if (typeof value === "number") {
3528
- return value.toString();
3529
- }
3530
- if (typeof value === "string") {
3531
- return quote ? escape(value) : value;
3532
- }
3533
- if (Array.isArray(value)) {
3534
- const content = value.map((it) => format(it, true)).join(", ");
3535
- return `[ ${content} ]`;
3536
- }
3537
- if (typeof value === "object") {
3538
- const content = Object.entries(value).map(([key, nested]) => `${key}: ${format(nested, true)}`).join(", ");
3539
- return `{ ${content} }`;
3540
- }
3541
- return String(value);
3542
- }
3543
- function interpolate(text, data) {
3544
- return text.replace(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
3545
- return typeof data[key] !== "undefined" ? format(data[key]) : match;
3546
- });
3547
- }
3548
-
3549
3353
  const cacheKey = Symbol("aria-naming");
3550
3354
  const defaultValue = "allowed";
3551
3355
  const prohibitedRoles = [
@@ -3587,6 +3391,31 @@ function ariaNaming(element) {
3587
3391
  return element.cacheSet(cacheKey, byMeta(element, meta));
3588
3392
  }
3589
3393
 
3394
+ const INPUT_DISABLED_CACHE = Symbol(isInputDisabled.name);
3395
+ function isInputDisabled(node, details) {
3396
+ const cached = node.cacheGet(INPUT_DISABLED_CACHE);
3397
+ if (cached) {
3398
+ return details ? cached : cached.byFieldset || cached.bySelf;
3399
+ }
3400
+ const result = node.cacheSet(INPUT_DISABLED_CACHE, isInputDisabledImpl(node));
3401
+ return details ? result : result.byFieldset || result.bySelf;
3402
+ }
3403
+ function isInputDisabledImpl(node) {
3404
+ const hasDisabledAttr = (node2) => {
3405
+ const disabled = node2.getAttribute("disabled");
3406
+ return Boolean(disabled?.isStatic);
3407
+ };
3408
+ const hasDisabledFieldset = (node2) => {
3409
+ const fieldset = node2.closest("fieldset[disabled]");
3410
+ const disabled = fieldset?.getAttribute("disabled");
3411
+ return Boolean(disabled?.isStatic);
3412
+ };
3413
+ return {
3414
+ byFieldset: hasDisabledFieldset(node),
3415
+ bySelf: hasDisabledAttr(node)
3416
+ };
3417
+ }
3418
+
3590
3419
  const patternCache = /* @__PURE__ */ new Map();
3591
3420
  function compileStringPattern(pattern) {
3592
3421
  const regexp = pattern.replace(/[*]+/g, ".+");
@@ -3651,7 +3480,7 @@ function inAccessibilityTree(node) {
3651
3480
  function isAriaHiddenImpl(node) {
3652
3481
  const getAriaHiddenAttr = (node2) => {
3653
3482
  const ariaHidden = node2.getAttribute("aria-hidden");
3654
- return Boolean(ariaHidden && ariaHidden.value === "true");
3483
+ return ariaHidden?.value === "true";
3655
3484
  };
3656
3485
  return {
3657
3486
  byParent: node.parent ? isAriaHidden(node.parent) : false,
@@ -3840,6 +3669,35 @@ function partition(values, predicate) {
3840
3669
  }, initial);
3841
3670
  }
3842
3671
 
3672
+ function escape(value) {
3673
+ return JSON.stringify(value);
3674
+ }
3675
+ function format(value, quote = false) {
3676
+ if (value === null || value === void 0) {
3677
+ return "null";
3678
+ }
3679
+ if (typeof value === "number") {
3680
+ return value.toString();
3681
+ }
3682
+ if (typeof value === "string") {
3683
+ return quote ? escape(value) : value;
3684
+ }
3685
+ if (Array.isArray(value)) {
3686
+ const content = value.map((it) => format(it, true)).join(", ");
3687
+ return `[ ${content} ]`;
3688
+ }
3689
+ if (typeof value === "object") {
3690
+ const content = Object.entries(value).map(([key, nested]) => `${key}: ${format(nested, true)}`).join(", ");
3691
+ return `{ ${content} }`;
3692
+ }
3693
+ return String(value);
3694
+ }
3695
+ function interpolate(text, data) {
3696
+ return text.replace(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
3697
+ return typeof data[key] !== "undefined" ? format(data[key]) : match;
3698
+ });
3699
+ }
3700
+
3843
3701
  const ajv$1 = new Ajv__default.default({ strict: true, strictTuples: true, strictTypes: true });
3844
3702
  ajv$1.addMetaSchema(ajvSchemaDraft);
3845
3703
  function getSchemaValidator(ruleId, properties) {
@@ -3857,7 +3715,7 @@ function getSchemaValidator(ruleId, properties) {
3857
3715
  return ajv$1.compile(schema);
3858
3716
  }
3859
3717
  function isErrorDescriptor(value) {
3860
- return Boolean(value[0] && value[0].message);
3718
+ return Boolean(value[0] && value[0]["message"]);
3861
3719
  }
3862
3720
  function unpackErrorDescriptor(value) {
3863
3721
  if (isErrorDescriptor(value)) {
@@ -4128,8 +3986,7 @@ class Rule {
4128
3986
  * @returns Rule documentation and url with additional details or `null` if no
4129
3987
  * additional documentation is available.
4130
3988
  */
4131
- /* eslint-disable-next-line @typescript-eslint/no-unused-vars -- technical debt, prototype should be moved to interface */
4132
- documentation(context) {
3989
+ documentation(_context) {
4133
3990
  return null;
4134
3991
  }
4135
3992
  }
@@ -4458,7 +4315,7 @@ class AriaHiddenBody extends Rule {
4458
4315
  const defaults$w = {
4459
4316
  allowAnyNamable: false
4460
4317
  };
4461
- const whitelisted = [
4318
+ const allowlist = /* @__PURE__ */ new Set([
4462
4319
  "main",
4463
4320
  "nav",
4464
4321
  "table",
@@ -4471,18 +4328,19 @@ const whitelisted = [
4471
4328
  "article",
4472
4329
  "dialog",
4473
4330
  "form",
4331
+ "iframe",
4474
4332
  "img",
4475
4333
  "area",
4476
4334
  "fieldset",
4477
4335
  "summary",
4478
4336
  "figure"
4479
- ];
4337
+ ]);
4480
4338
  function isValidUsage(target, meta) {
4481
4339
  const explicit = meta.attributes["aria-label"];
4482
4340
  if (explicit) {
4483
4341
  return true;
4484
4342
  }
4485
- if (whitelisted.includes(target.tagName)) {
4343
+ if (allowlist.has(target.tagName)) {
4486
4344
  return true;
4487
4345
  }
4488
4346
  if (target.hasAttribute("role")) {
@@ -4500,7 +4358,7 @@ class AriaLabelMisuse extends Rule {
4500
4358
  constructor(options) {
4501
4359
  super({ ...defaults$w, ...options });
4502
4360
  }
4503
- documentation() {
4361
+ documentation(context) {
4504
4362
  const valid = [
4505
4363
  "Interactive elements",
4506
4364
  "Labelable elements",
@@ -4514,25 +4372,41 @@ class AriaLabelMisuse extends Rule {
4514
4372
  "`<summary>`",
4515
4373
  "`<table>`, `<td>` and `<th>`"
4516
4374
  ];
4517
- const lines = valid.map((it) => `- ${it}
4518
- `).join("");
4519
- return {
4520
- description: `\`aria-label\` can only be used on:
4521
-
4522
- ${lines}`,
4523
- url: "https://html-validate.org/rules/aria-label-misuse.html"
4524
- };
4375
+ const lines = valid.map((it) => `- ${it}`);
4376
+ const url = "https://html-validate.org/rules/aria-label-misuse.html";
4377
+ if (context.allowsNaming) {
4378
+ return {
4379
+ description: [
4380
+ `\`${context.attr}\` is strictly allowed but is not recommended to be used on this element.`,
4381
+ `\`${context.attr}\` can only be used on:`,
4382
+ "",
4383
+ ...lines
4384
+ ].join("\n"),
4385
+ url
4386
+ };
4387
+ } else {
4388
+ return {
4389
+ description: [`\`${context.attr}\` can only be used on:`, "", ...lines].join("\n"),
4390
+ url
4391
+ };
4392
+ }
4525
4393
  }
4526
4394
  setup() {
4527
4395
  this.on("dom:ready", (event) => {
4528
4396
  const { document } = event;
4529
- for (const target of document.querySelectorAll("[aria-label]")) {
4530
- this.validateElement(target);
4397
+ for (const target of document.querySelectorAll("[aria-label], [aria-labelledby]")) {
4398
+ const ariaLabel = target.getAttribute("aria-label");
4399
+ if (ariaLabel) {
4400
+ this.validateElement(target, ariaLabel, "aria-label");
4401
+ }
4402
+ const ariaLabelledby = target.getAttribute("aria-labelledby");
4403
+ if (ariaLabelledby) {
4404
+ this.validateElement(target, ariaLabelledby, "aria-labelledby");
4405
+ }
4531
4406
  }
4532
4407
  });
4533
4408
  }
4534
- validateElement(target) {
4535
- const attr = target.getAttribute("aria-label");
4409
+ validateElement(target, attr, key) {
4536
4410
  if (!attr.value || attr.valueMatches("", false)) {
4537
4411
  return;
4538
4412
  }
@@ -4543,10 +4417,26 @@ ${lines}`,
4543
4417
  if (isValidUsage(target, meta)) {
4544
4418
  return;
4545
4419
  }
4546
- if (this.options.allowAnyNamable && ariaNaming(target) === "allowed") {
4420
+ const allowsNaming = ariaNaming(target) === "allowed";
4421
+ if (allowsNaming && this.options.allowAnyNamable) {
4547
4422
  return;
4548
4423
  }
4549
- this.report(target, `"aria-label" cannot be used on this element`, attr.keyLocation);
4424
+ const context = { attr: key, allowsNaming };
4425
+ if (allowsNaming) {
4426
+ this.report({
4427
+ node: target,
4428
+ location: attr.keyLocation,
4429
+ context,
4430
+ message: `"{{ attr }}" is strictly allowed but is not recommended to be used on this element`
4431
+ });
4432
+ } else {
4433
+ this.report({
4434
+ node: target,
4435
+ location: attr.keyLocation,
4436
+ context,
4437
+ message: `"{{ attr }}" cannot be used on this element`
4438
+ });
4439
+ }
4550
4440
  }
4551
4441
  }
4552
4442
 
@@ -5241,9 +5131,6 @@ class AttributeAllowedValues extends Rule {
5241
5131
  description: "Attribute has invalid value.",
5242
5132
  url: "https://html-validate.org/rules/attribute-allowed-values.html"
5243
5133
  };
5244
- if (!context) {
5245
- return docs;
5246
- }
5247
5134
  const { allowed, attribute, element, value } = context;
5248
5135
  if (allowed.enum) {
5249
5136
  const allowedList = allowed.enum.map((value2) => {
@@ -6078,7 +5965,10 @@ class ElementName extends Rule {
6078
5965
 
6079
5966
  function isNativeTemplate(node) {
6080
5967
  const { tagName, meta } = node;
6081
- return Boolean(tagName === "template" && meta?.templateRoot && meta?.scriptSupporting);
5968
+ if (!meta) {
5969
+ return false;
5970
+ }
5971
+ return Boolean(tagName === "template" && meta.templateRoot && meta.scriptSupporting);
6082
5972
  }
6083
5973
  function getTransparentChildren(node, transparent) {
6084
5974
  if (typeof transparent === "boolean") {
@@ -6307,7 +6197,7 @@ class ElementPermittedParent extends Rule {
6307
6197
  }
6308
6198
  const rules = node.meta?.permittedParent;
6309
6199
  if (!rules) {
6310
- return false;
6200
+ return;
6311
6201
  }
6312
6202
  if (Validator.validatePermitted(parent, rules)) {
6313
6203
  return;
@@ -6374,14 +6264,10 @@ class ElementRequiredAncestor extends Rule {
6374
6264
 
6375
6265
  class ElementRequiredAttributes extends Rule {
6376
6266
  documentation(context) {
6377
- const docs = {
6378
- description: "Element is missing a required attribute",
6267
+ return {
6268
+ description: `The \`<${context.element}>\` element is required to have a \`${context.attribute}\` attribute.`,
6379
6269
  url: "https://html-validate.org/rules/element-required-attributes.html"
6380
6270
  };
6381
- if (context) {
6382
- docs.description = `The <${context.element}> element is required to have a "${context.attribute}" attribute.`;
6383
- }
6384
- return docs;
6385
6271
  }
6386
6272
  setup() {
6387
6273
  this.on("tag:end", (event) => {
@@ -6528,6 +6414,18 @@ const defaults$l = {
6528
6414
  };
6529
6415
  const UNIQUE_CACHE_KEY = Symbol("form-elements-unique");
6530
6416
  const SHARED_CACHE_KEY = Symbol("form-elements-shared");
6417
+ function isEnabled(element) {
6418
+ if (isHTMLHidden(element)) {
6419
+ return false;
6420
+ }
6421
+ if (isInert(element)) {
6422
+ return false;
6423
+ }
6424
+ if (isInputDisabled(element)) {
6425
+ return false;
6426
+ }
6427
+ return true;
6428
+ }
6531
6429
  function haveName(name) {
6532
6430
  return typeof name === "string" && name !== "";
6533
6431
  }
@@ -6598,7 +6496,7 @@ class FormDupName extends Rule {
6598
6496
  const { shared } = this.options;
6599
6497
  this.on("dom:ready", (event) => {
6600
6498
  const { document } = event;
6601
- const controls = document.querySelectorAll(selector);
6499
+ const controls = document.querySelectorAll(selector).filter(isEnabled);
6602
6500
  const [sharedControls, uniqueControls] = partition(controls, (it) => {
6603
6501
  return allowSharedName(it, shared);
6604
6502
  });
@@ -7354,69 +7252,6 @@ class LongTitle extends Rule {
7354
7252
  }
7355
7253
  }
7356
7254
 
7357
- const defaults$h = {
7358
- allowLongDelay: false
7359
- };
7360
- class MetaRefresh extends Rule {
7361
- constructor(options) {
7362
- super({ ...defaults$h, ...options });
7363
- }
7364
- documentation() {
7365
- return {
7366
- description: `Meta refresh directive must use the \`0;url=...\` format. Non-zero values for time interval is disallowed as people with assistive technology might be unable to read and understand the page content before automatically reloading. For the same reason skipping the url is disallowed as it would put the browser in an infinite loop reloading the same page over and over again.`,
7367
- url: "https://html-validate.org/rules/meta-refresh.html"
7368
- };
7369
- }
7370
- setup() {
7371
- this.on("element:ready", ({ target }) => {
7372
- if (!target.is("meta")) {
7373
- return;
7374
- }
7375
- const httpEquiv = target.getAttributeValue("http-equiv");
7376
- if (httpEquiv !== "refresh") {
7377
- return;
7378
- }
7379
- const content = target.getAttribute("content");
7380
- if (!content?.value || content.isDynamic) {
7381
- return;
7382
- }
7383
- const location = content.valueLocation;
7384
- const value = parseContent(content.value.toString());
7385
- if (!value) {
7386
- this.report(target, "Malformed meta refresh directive", location);
7387
- return;
7388
- }
7389
- const { delay, url } = value;
7390
- this.validateDelay(target, location, delay, url);
7391
- });
7392
- }
7393
- validateDelay(target, location, delay, url) {
7394
- const { allowLongDelay } = this.options;
7395
- if (allowLongDelay && delay > 72e3) {
7396
- return;
7397
- }
7398
- if (!url && delay === 0) {
7399
- this.report(target, "Don't use instant meta refresh to reload the page", location);
7400
- return;
7401
- }
7402
- if (delay !== 0) {
7403
- const message = allowLongDelay ? "Meta refresh must be instant (0 second delay) or greater than 20 hours (72000 second delay)" : "Meta refresh must be instant (0 second delay)";
7404
- this.report(target, message, location);
7405
- }
7406
- }
7407
- }
7408
- function parseContent(text) {
7409
- const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7410
- if (match) {
7411
- return {
7412
- delay: parseInt(match[1], 10),
7413
- url: match[2]
7414
- };
7415
- } else {
7416
- return null;
7417
- }
7418
- }
7419
-
7420
7255
  function getName(attr) {
7421
7256
  const name = attr.value;
7422
7257
  if (!name || name instanceof DynamicValue) {
@@ -7479,16 +7314,79 @@ class MapIdName extends Rule {
7479
7314
  if (!hasStaticValue(id) || !hasStaticValue(name)) {
7480
7315
  return;
7481
7316
  }
7482
- if (id.value === name.value) {
7317
+ if (id.value === name.value) {
7318
+ return;
7319
+ }
7320
+ this.report({
7321
+ node: event.target,
7322
+ message: `"id" and "name" attribute must be the same on <map> elements`,
7323
+ location: id.valueLocation ?? name.valueLocation
7324
+ });
7325
+ });
7326
+ }
7327
+ }
7328
+
7329
+ const defaults$h = {
7330
+ allowLongDelay: false
7331
+ };
7332
+ class MetaRefresh extends Rule {
7333
+ constructor(options) {
7334
+ super({ ...defaults$h, ...options });
7335
+ }
7336
+ documentation() {
7337
+ return {
7338
+ description: `Meta refresh directive must use the \`0;url=...\` format. Non-zero values for time interval is disallowed as people with assistive technology might be unable to read and understand the page content before automatically reloading. For the same reason skipping the url is disallowed as it would put the browser in an infinite loop reloading the same page over and over again.`,
7339
+ url: "https://html-validate.org/rules/meta-refresh.html"
7340
+ };
7341
+ }
7342
+ setup() {
7343
+ this.on("element:ready", ({ target }) => {
7344
+ if (!target.is("meta")) {
7345
+ return;
7346
+ }
7347
+ const httpEquiv = target.getAttributeValue("http-equiv");
7348
+ if (httpEquiv !== "refresh") {
7349
+ return;
7350
+ }
7351
+ const content = target.getAttribute("content");
7352
+ if (!content?.value || content.isDynamic) {
7353
+ return;
7354
+ }
7355
+ const location = content.valueLocation;
7356
+ const value = parseContent(content.value.toString());
7357
+ if (!value) {
7358
+ this.report(target, "Malformed meta refresh directive", location);
7483
7359
  return;
7484
7360
  }
7485
- this.report({
7486
- node: event.target,
7487
- message: `"id" and "name" attribute must be the same on <map> elements`,
7488
- location: id.valueLocation ?? name.valueLocation
7489
- });
7361
+ const { delay, url } = value;
7362
+ this.validateDelay(target, location, delay, url);
7490
7363
  });
7491
7364
  }
7365
+ validateDelay(target, location, delay, url) {
7366
+ const { allowLongDelay } = this.options;
7367
+ if (allowLongDelay && delay > 72e3) {
7368
+ return;
7369
+ }
7370
+ if (!url && delay === 0) {
7371
+ this.report(target, "Don't use instant meta refresh to reload the page", location);
7372
+ return;
7373
+ }
7374
+ if (delay !== 0) {
7375
+ const message = allowLongDelay ? "Meta refresh must be instant (0 second delay) or greater than 20 hours (72000 second delay)" : "Meta refresh must be instant (0 second delay)";
7376
+ this.report(target, message, location);
7377
+ }
7378
+ }
7379
+ }
7380
+ function parseContent(text) {
7381
+ const match = /^(\d+)(?:\s*;\s*url=(.*))?/i.exec(text);
7382
+ if (match) {
7383
+ return {
7384
+ delay: parseInt(match[1], 10),
7385
+ url: match[2]
7386
+ };
7387
+ } else {
7388
+ return null;
7389
+ }
7492
7390
  }
7493
7391
 
7494
7392
  class MissingDoctype extends Rule {
@@ -7887,30 +7785,6 @@ class NoImplicitButtonType extends Rule {
7887
7785
  }
7888
7786
  }
7889
7787
 
7890
- function isRelevant$1(event) {
7891
- return event.target.is("input");
7892
- }
7893
- class NoImplicitInputType extends Rule {
7894
- documentation() {
7895
- return {
7896
- description: ["`<input>` is missing recommended `type` attribute"].join("\n"),
7897
- url: "https://html-validate.org/rules/no-implicit-input-type.html"
7898
- };
7899
- }
7900
- setup() {
7901
- this.on("element:ready", isRelevant$1, (event) => {
7902
- const { target } = event;
7903
- const attr = target.getAttribute("type");
7904
- if (!attr) {
7905
- this.report({
7906
- node: event.target,
7907
- message: `<input> is missing recommended "type" attribute`
7908
- });
7909
- }
7910
- });
7911
- }
7912
- }
7913
-
7914
7788
  class NoImplicitClose extends Rule {
7915
7789
  documentation() {
7916
7790
  return {
@@ -7963,6 +7837,30 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
7963
7837
  }
7964
7838
  }
7965
7839
 
7840
+ function isRelevant$1(event) {
7841
+ return event.target.is("input");
7842
+ }
7843
+ class NoImplicitInputType extends Rule {
7844
+ documentation() {
7845
+ return {
7846
+ description: ["`<input>` is missing recommended `type` attribute"].join("\n"),
7847
+ url: "https://html-validate.org/rules/no-implicit-input-type.html"
7848
+ };
7849
+ }
7850
+ setup() {
7851
+ this.on("element:ready", isRelevant$1, (event) => {
7852
+ const { target } = event;
7853
+ const attr = target.getAttribute("type");
7854
+ if (!attr) {
7855
+ this.report({
7856
+ node: event.target,
7857
+ message: `<input> is missing recommended "type" attribute`
7858
+ });
7859
+ }
7860
+ });
7861
+ }
7862
+ }
7863
+
7966
7864
  const defaults$e = {
7967
7865
  include: null,
7968
7866
  exclude: null,
@@ -8372,10 +8270,9 @@ class NoSelfClosing extends Rule {
8372
8270
  }
8373
8271
  };
8374
8272
  }
8375
- documentation(tagName) {
8376
- tagName = tagName || "element";
8273
+ documentation(context) {
8377
8274
  return {
8378
- description: `Self-closing elements are disallowed. Use regular end tag <${tagName}></${tagName}> instead of self-closing <${tagName}/>.`,
8275
+ description: `Self-closing elements are disallowed. Use regular end tag <${context}></${context}> instead of self-closing <${context}/>.`,
8379
8276
  url: "https://html-validate.org/rules/no-self-closing.html"
8380
8277
  };
8381
8278
  }
@@ -8953,7 +8850,7 @@ class ScriptElement extends Rule {
8953
8850
  setup() {
8954
8851
  this.on("tag:end", (event) => {
8955
8852
  const node = event.target;
8956
- if (!node || node.tagName !== "script") {
8853
+ if (node?.tagName !== "script") {
8957
8854
  return;
8958
8855
  }
8959
8856
  if (node.closed !== NodeClosed.EndTag) {
@@ -10166,13 +10063,13 @@ class ValidID extends Rule {
10166
10063
  }
10167
10064
 
10168
10065
  class VoidContent extends Rule {
10169
- documentation(tagName) {
10066
+ documentation(context) {
10170
10067
  const doc = {
10171
10068
  description: "HTML void elements cannot have any content and must not have content or end tag.",
10172
10069
  url: "https://html-validate.org/rules/void-content.html"
10173
10070
  };
10174
- if (tagName) {
10175
- doc.description = `<${tagName}> is a void element and must not have content or end tag.`;
10071
+ if (context) {
10072
+ doc.description = `<${context}> is a void element and must not have content or end tag.`;
10176
10073
  }
10177
10074
  return doc;
10178
10075
  }
@@ -10475,7 +10372,7 @@ class H37 extends Rule {
10475
10372
  const defaults$1 = {
10476
10373
  strict: false
10477
10374
  };
10478
- const { enum: validScopes } = elements.html5.th.attributes?.scope;
10375
+ const { enum: validScopes } = elements.html5.th.attributes.scope;
10479
10376
  const joinedScopes = utils_naturalJoin.naturalJoin(validScopes);
10480
10377
  const td = 0;
10481
10378
  const th = 1;
@@ -10573,7 +10470,7 @@ class H67 extends Rule {
10573
10470
  setup() {
10574
10471
  this.on("tag:end", (event) => {
10575
10472
  const node = event.target;
10576
- if (!node || node.tagName !== "img") {
10473
+ if (node?.tagName !== "img") {
10577
10474
  return;
10578
10475
  }
10579
10476
  const title = node.getAttribute("title");
@@ -10716,6 +10613,172 @@ const bundledRules = {
10716
10613
  ...bundledRules$1
10717
10614
  };
10718
10615
 
10616
+ const $schema = "http://json-schema.org/draft-06/schema#";
10617
+ const $id = "https://html-validate.org/schemas/config.json";
10618
+ const type = "object";
10619
+ const additionalProperties = false;
10620
+ const properties = {
10621
+ $schema: {
10622
+ type: "string"
10623
+ },
10624
+ root: {
10625
+ type: "boolean",
10626
+ title: "Mark as root configuration",
10627
+ description: "If this is set to true no further configurations will be searched.",
10628
+ "default": false
10629
+ },
10630
+ "extends": {
10631
+ type: "array",
10632
+ items: {
10633
+ type: "string"
10634
+ },
10635
+ title: "Configurations to extend",
10636
+ description: "Array of shareable or builtin configurations to extend."
10637
+ },
10638
+ elements: {
10639
+ type: "array",
10640
+ items: {
10641
+ anyOf: [
10642
+ {
10643
+ type: "string"
10644
+ },
10645
+ {
10646
+ type: "object"
10647
+ }
10648
+ ]
10649
+ },
10650
+ title: "Element metadata to load",
10651
+ description: "Array of modules, plugins or files to load element metadata from. Use <rootDir> to refer to the folder with the package.json file.",
10652
+ examples: [
10653
+ [
10654
+ "html-validate:recommended",
10655
+ "plugin:recommended",
10656
+ "module",
10657
+ "./local-file.json"
10658
+ ]
10659
+ ]
10660
+ },
10661
+ plugins: {
10662
+ type: "array",
10663
+ items: {
10664
+ anyOf: [
10665
+ {
10666
+ type: "string"
10667
+ },
10668
+ {
10669
+ type: "object"
10670
+ }
10671
+ ]
10672
+ },
10673
+ title: "Plugins to load",
10674
+ description: "Array of plugins load. Use <rootDir> to refer to the folder with the package.json file.",
10675
+ examples: [
10676
+ [
10677
+ "my-plugin",
10678
+ "./local-plugin"
10679
+ ]
10680
+ ]
10681
+ },
10682
+ transform: {
10683
+ type: "object",
10684
+ additionalProperties: {
10685
+ anyOf: [
10686
+ {
10687
+ type: "string"
10688
+ },
10689
+ {
10690
+ "function": true
10691
+ }
10692
+ ]
10693
+ },
10694
+ title: "File transformations to use.",
10695
+ description: "Object where key is regular expression to match filename and value is name of transformer or a function.",
10696
+ examples: [
10697
+ {
10698
+ "^.*\\.foo$": "my-transformer",
10699
+ "^.*\\.bar$": "my-plugin",
10700
+ "^.*\\.baz$": "my-plugin:named"
10701
+ }
10702
+ ]
10703
+ },
10704
+ rules: {
10705
+ type: "object",
10706
+ patternProperties: {
10707
+ ".*": {
10708
+ anyOf: [
10709
+ {
10710
+ "enum": [
10711
+ 0,
10712
+ 1,
10713
+ 2,
10714
+ "off",
10715
+ "warn",
10716
+ "error"
10717
+ ]
10718
+ },
10719
+ {
10720
+ type: "array",
10721
+ minItems: 1,
10722
+ maxItems: 1,
10723
+ items: [
10724
+ {
10725
+ "enum": [
10726
+ 0,
10727
+ 1,
10728
+ 2,
10729
+ "off",
10730
+ "warn",
10731
+ "error"
10732
+ ]
10733
+ }
10734
+ ]
10735
+ },
10736
+ {
10737
+ type: "array",
10738
+ minItems: 2,
10739
+ maxItems: 2,
10740
+ items: [
10741
+ {
10742
+ "enum": [
10743
+ 0,
10744
+ 1,
10745
+ 2,
10746
+ "off",
10747
+ "warn",
10748
+ "error"
10749
+ ]
10750
+ },
10751
+ {
10752
+ }
10753
+ ]
10754
+ }
10755
+ ]
10756
+ }
10757
+ },
10758
+ title: "Rule configuration.",
10759
+ description: "Enable/disable rules, set severity. Some rules have additional configuration like style or patterns to use.",
10760
+ examples: [
10761
+ {
10762
+ foo: "error",
10763
+ bar: "off",
10764
+ baz: [
10765
+ "error",
10766
+ {
10767
+ style: "camelcase"
10768
+ }
10769
+ ]
10770
+ }
10771
+ ]
10772
+ }
10773
+ };
10774
+ var configurationSchema = {
10775
+ $schema: $schema,
10776
+ $id: $id,
10777
+ type: type,
10778
+ additionalProperties: additionalProperties,
10779
+ properties: properties
10780
+ };
10781
+
10719
10782
  function dumpTree(root) {
10720
10783
  const lines = [];
10721
10784
  function decoration(node) {
@@ -11851,7 +11914,7 @@ class EventHandler {
11851
11914
  }
11852
11915
 
11853
11916
  const name = "html-validate";
11854
- const version = "10.1.1";
11917
+ const version = "10.2.1";
11855
11918
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11856
11919
 
11857
11920
  function freeze(src) {
@@ -12584,9 +12647,9 @@ class Parser {
12584
12647
  * Trigger close events for any still open elements.
12585
12648
  */
12586
12649
  closeTree(source, location) {
12587
- let active;
12588
12650
  const documentElement = this.dom.root;
12589
- while ((active = this.dom.getActive()) && !active.isRootElement()) {
12651
+ let active = this.dom.getActive();
12652
+ while (!active.isRootElement()) {
12590
12653
  if (active.meta?.implicitClosed) {
12591
12654
  active.closed = NodeClosed.ImplicitClosed;
12592
12655
  this.closeElement(source, documentElement, active, location);
@@ -12594,6 +12657,7 @@ class Parser {
12594
12657
  this.closeElement(source, null, active, location);
12595
12658
  }
12596
12659
  this.dom.popActive();
12660
+ active = this.dom.getActive();
12597
12661
  }
12598
12662
  }
12599
12663
  }
@@ -12960,8 +13024,8 @@ function getUnnamedTransformerFromPlugin(name, plugin) {
12960
13024
  throw new ConfigError(`Plugin does not expose any transformers`);
12961
13025
  }
12962
13026
  if (typeof plugin.transformer !== "function") {
12963
- if (plugin.transformer.default) {
12964
- return plugin.transformer.default;
13027
+ if (plugin.transformer["default"]) {
13028
+ return plugin.transformer["default"];
12965
13029
  }
12966
13030
  throw new ConfigError(
12967
13031
  `Transformer "${name}" refers to unnamed transformer but plugin exposes only named.`
@@ -13190,7 +13254,7 @@ function checkstyleFormatter(results) {
13190
13254
  output += "</checkstyle>\n";
13191
13255
  return output;
13192
13256
  }
13193
- const formatter$3 = checkstyleFormatter;
13257
+ const formatter$2 = checkstyleFormatter;
13194
13258
 
13195
13259
  const defaults = {
13196
13260
  showLink: true,
@@ -13365,7 +13429,7 @@ function codeframe(results, options) {
13365
13429
  function jsonFormatter(results) {
13366
13430
  return JSON.stringify(results);
13367
13431
  }
13368
- const formatter$2 = jsonFormatter;
13432
+ const formatter$1 = jsonFormatter;
13369
13433
 
13370
13434
  function linkSummary(results) {
13371
13435
  const urls = results.reduce((result, it) => {
@@ -13394,7 +13458,6 @@ function stylish(results) {
13394
13458
  const links = linkSummary(results);
13395
13459
  return `${errors}${links}`;
13396
13460
  }
13397
- const formatter$1 = stylish;
13398
13461
 
13399
13462
  function textFormatter(results) {
13400
13463
  let output = "";
@@ -13424,10 +13487,10 @@ function textFormatter(results) {
13424
13487
  const formatter = textFormatter;
13425
13488
 
13426
13489
  const availableFormatters = {
13427
- checkstyle: formatter$3,
13490
+ checkstyle: formatter$2,
13428
13491
  codeframe,
13429
- json: formatter$2,
13430
- stylish: formatter$1,
13492
+ json: formatter$1,
13493
+ stylish,
13431
13494
  text: formatter
13432
13495
  };
13433
13496
  function getFormatter(name) {