html-validate 6.1.6 → 6.3.2

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.
package/dist/es/core.d.ts CHANGED
@@ -1575,7 +1575,8 @@ declare class MetaTable {
1575
1575
  */
1576
1576
  loadFromFile(filename: string): void;
1577
1577
  /**
1578
- * Get [[MetaElement]] for the given tag or null if the element doesn't exist.
1578
+ * Get [[MetaElement]] for the given tag. If no specific metadata is present
1579
+ * the global metadata is returned or null if no global is present.
1579
1580
  *
1580
1581
  * @public
1581
1582
  * @returns A shallow copy of metadata.
package/dist/es/core.js CHANGED
@@ -933,14 +933,23 @@ class MetaTable {
933
933
  }
934
934
  }
935
935
  /**
936
- * Get [[MetaElement]] for the given tag or null if the element doesn't exist.
936
+ * Get [[MetaElement]] for the given tag. If no specific metadata is present
937
+ * the global metadata is returned or null if no global is present.
937
938
  *
938
939
  * @public
939
940
  * @returns A shallow copy of metadata.
940
941
  */
941
942
  getMetaFor(tagName) {
943
+ /* try to locate by tagname */
942
944
  tagName = tagName.toLowerCase();
943
- return this.elements[tagName] ? { ...this.elements[tagName] } : null;
945
+ if (this.elements[tagName]) {
946
+ return { ...this.elements[tagName] };
947
+ }
948
+ /* try to locate global element */
949
+ if (this.elements["*"]) {
950
+ return { ...this.elements["*"] };
951
+ }
952
+ return null;
944
953
  }
945
954
  /**
946
955
  * Find all tags which has enabled given property.
@@ -1772,6 +1781,14 @@ class Selector {
1772
1781
  }
1773
1782
 
1774
1783
  const TEXT_NODE_NAME = "#text";
1784
+ /**
1785
+ * Returns true if the node is a text node.
1786
+ *
1787
+ * @public
1788
+ */
1789
+ function isTextNode(node) {
1790
+ return Boolean(node && node.nodeType === NodeType.TEXT_NODE);
1791
+ }
1775
1792
  /**
1776
1793
  * Represents a text in the HTML document.
1777
1794
  *
@@ -2928,7 +2945,7 @@ var TRANSFORMER_API;
2928
2945
  /** @public */
2929
2946
  const name = "html-validate";
2930
2947
  /** @public */
2931
- const version = "6.1.6";
2948
+ const version = "6.3.2";
2932
2949
  /** @public */
2933
2950
  const homepage = "https://html-validate.org";
2934
2951
  /** @public */
@@ -3208,7 +3225,8 @@ function ruleDocumentationUrl(filename) {
3208
3225
  const p = path.parse(filename);
3209
3226
  const root = path.join(distFolder, "rules");
3210
3227
  const rel = path.relative(root, path.join(p.dir, p.name));
3211
- return `${homepage}/rules/${rel}.html`;
3228
+ const normalized = rel.replace(/\\/g, "/");
3229
+ return `${homepage}/rules/${normalized}.html`;
3212
3230
  }
3213
3231
 
3214
3232
  const defaults$p = {
@@ -3398,6 +3416,28 @@ class AllowedLinks extends Rule {
3398
3416
  }
3399
3417
  }
3400
3418
 
3419
+ class AriaHiddenBody extends Rule {
3420
+ documentation() {
3421
+ return {
3422
+ description: "`aria-hidden` must not be used on the `<body>` element as it makes the page inaccessible to assistive technology such as screenreaders",
3423
+ url: ruleDocumentationUrl("@/rules/aria-hidden-body.ts"),
3424
+ };
3425
+ }
3426
+ setup() {
3427
+ this.on("tag:ready", this.isRelevant, (event) => {
3428
+ const { target } = event;
3429
+ const attr = target.getAttribute("aria-hidden");
3430
+ if (!attr || !attr.valueMatches("true", true)) {
3431
+ return;
3432
+ }
3433
+ this.report(target, "aria-hidden must not be used on <body>", attr.keyLocation);
3434
+ });
3435
+ }
3436
+ isRelevant(event) {
3437
+ return event.target.is("body");
3438
+ }
3439
+ }
3440
+
3401
3441
  const whitelisted = [
3402
3442
  "main",
3403
3443
  "nav",
@@ -4074,12 +4114,26 @@ class AttributeAllowedValues extends Rule {
4074
4114
  if (!context) {
4075
4115
  return docs;
4076
4116
  }
4077
- if (context.allowed.enum) {
4078
- const allowed = context.allowed.enum.map((val) => `- \`${val}\``);
4079
- docs.description = `Element <${context.element}> does not allow attribute \`${context.attribute}\` to have the value \`"${context.value}"\`, it must match one of the following:\n\n${allowed.join("\n")}`;
4117
+ const { allowed, attribute, element, value } = context;
4118
+ if (allowed.enum) {
4119
+ const allowedList = allowed.enum.map((value) => {
4120
+ if (typeof value === "string") {
4121
+ return `- \`"${value}"\``;
4122
+ }
4123
+ else {
4124
+ return `- \`${value.toString()}\``;
4125
+ }
4126
+ });
4127
+ docs.description = [
4128
+ `The \`<${element}>\` element does not allow the attribute \`${attribute}\` to have the value \`"${value}"\`.`,
4129
+ "",
4130
+ "It must match one of the following:",
4131
+ "",
4132
+ ...allowedList,
4133
+ ].join("\n");
4080
4134
  }
4081
- else if (context.allowed.boolean) {
4082
- docs.description = `Element <${context.element}> attribute \`${context.attribute}\` must be a boolean attribute, e.g. \`<${context.element} ${context.attribute}>\``;
4135
+ else if (allowed.boolean) {
4136
+ docs.description = `The \`<${context.element}>\` attribute \`${context.attribute}\` must be a boolean attribute, e.g. \`<${context.element} ${context.attribute}>\``;
4083
4137
  }
4084
4138
  return docs;
4085
4139
  }
@@ -5011,8 +5065,9 @@ class ElementRequiredAttributes extends Rule {
5011
5065
  class ElementRequiredContent extends Rule {
5012
5066
  documentation(context) {
5013
5067
  if (context) {
5068
+ const { element, missing } = context;
5014
5069
  return {
5015
- description: `The <${context.node} element requires a <${context.missing}> to be present as content.`,
5070
+ description: `The \`${element}\` element requires a \`${missing}\` to be present as content.`,
5016
5071
  url: ruleDocumentationUrl("@/rules/element-required-content.ts"),
5017
5072
  };
5018
5073
  }
@@ -5038,10 +5093,11 @@ class ElementRequiredContent extends Rule {
5038
5093
  }
5039
5094
  for (const missing of Validator.validateRequiredContent(node, rules)) {
5040
5095
  const context = {
5041
- node: node.tagName,
5042
- missing,
5096
+ element: node.annotatedName,
5097
+ missing: `<${missing}>`,
5043
5098
  };
5044
- this.report(node, `${node.annotatedName} element must have <${missing}> as content`, null, context);
5099
+ const message = `${node.annotatedName} element must have <${missing}> as content`;
5100
+ this.report(node, message, null, context);
5045
5101
  }
5046
5102
  });
5047
5103
  });
@@ -5124,7 +5180,14 @@ class EmptyHeading extends Rule {
5124
5180
  class EmptyTitle extends Rule {
5125
5181
  documentation() {
5126
5182
  return {
5127
- description: `The <title> element is used to describe the document and is shown in the browser tab and titlebar. WCAG and SEO requires a descriptive title and preferably unique within the site. Whitespace only is considered empty.`,
5183
+ description: [
5184
+ "The `<title>` element cannot be empty, it must have textual content.",
5185
+ "",
5186
+ "It is used to describe the document and is shown in the browser tab and titlebar.",
5187
+ "WCAG and SEO requires a descriptive title and preferably unique within the site.",
5188
+ "",
5189
+ "Whitespace is ignored.",
5190
+ ].join("\n"),
5128
5191
  url: ruleDocumentationUrl("@/rules/empty-title.ts"),
5129
5192
  };
5130
5193
  }
@@ -5140,7 +5203,10 @@ class EmptyTitle extends Rule {
5140
5203
  break;
5141
5204
  case TextClassification.EMPTY_TEXT:
5142
5205
  /* no content or whitespace only */
5143
- this.report(node, `<${node.tagName}> cannot be empty, must have text content`);
5206
+ {
5207
+ const message = `<${node.tagName}> cannot be empty, must have text content`;
5208
+ this.report(node, message, node.location);
5209
+ }
5144
5210
  break;
5145
5211
  }
5146
5212
  });
@@ -7056,9 +7122,6 @@ function hasDefaultText(node) {
7056
7122
  const type = node.getAttribute("type");
7057
7123
  return Boolean(type && type.valueMatches(/submit|reset/, false));
7058
7124
  }
7059
- function isTextNode(node) {
7060
- return node.nodeType === NodeType.TEXT_NODE;
7061
- }
7062
7125
  function isNonEmptyText(node) {
7063
7126
  if (isTextNode(node)) {
7064
7127
  return node.isDynamic || node.textContent.trim() !== "";
@@ -9421,6 +9484,7 @@ const bundledRules$1 = {
9421
9484
 
9422
9485
  const bundledRules = {
9423
9486
  "allowed-links": AllowedLinks,
9487
+ "aria-hidden-body": AriaHiddenBody,
9424
9488
  "aria-label-misuse": AriaLabelMisuse,
9425
9489
  "attr-case": AttrCase,
9426
9490
  "attr-delimiter": AttrDelimiter,
@@ -9491,6 +9555,7 @@ var defaultConfig = {};
9491
9555
 
9492
9556
  const config$3 = {
9493
9557
  rules: {
9558
+ "aria-hidden-body": "error",
9494
9559
  "aria-label-misuse": "error",
9495
9560
  "deprecated-rule": "warn",
9496
9561
  "empty-heading": "error",
@@ -9525,6 +9590,7 @@ const config$2 = {
9525
9590
 
9526
9591
  const config$1 = {
9527
9592
  rules: {
9593
+ "aria-hidden-body": "error",
9528
9594
  "aria-label-misuse": "error",
9529
9595
  "attr-case": "error",
9530
9596
  "attr-delimiter": "error",
@@ -9694,7 +9760,9 @@ class ResolvedConfig {
9694
9760
  * @returns A list of transformed sources ready for validation.
9695
9761
  */
9696
9762
  transformFilename(filename) {
9697
- const data = fs.readFileSync(filename, { encoding: "utf8" });
9763
+ const stdin = 0;
9764
+ const src = filename !== "/dev/stdin" ? filename : stdin;
9765
+ const data = fs.readFileSync(src, { encoding: "utf8" });
9698
9766
  const source = {
9699
9767
  data,
9700
9768
  filename,