html-validate 8.18.2 → 8.19.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.
package/dist/cjs/core.js CHANGED
@@ -7,7 +7,7 @@ var utils_naturalJoin = require('./utils/natural-join.js');
7
7
  var fs = require('fs');
8
8
  var codeFrame = require('@babel/code-frame');
9
9
  var kleur = require('kleur');
10
- var stylish$2 = require('@html-validate/stylish');
10
+ var stylish$1 = require('@html-validate/stylish');
11
11
  var semver = require('semver');
12
12
 
13
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -6224,6 +6224,7 @@ class EmptyTitle extends Rule {
6224
6224
 
6225
6225
  const defaults$k = {
6226
6226
  allowArrayBrackets: true,
6227
+ allowCheckboxDefault: true,
6227
6228
  shared: ["radio", "button", "reset", "submit"]
6228
6229
  };
6229
6230
  const UNIQUE_CACHE_KEY = Symbol("form-elements-unique");
@@ -6235,6 +6236,25 @@ function allowSharedName(node, shared) {
6235
6236
  const type = node.getAttribute("type");
6236
6237
  return Boolean(type == null ? void 0 : type.valueMatches(shared, false));
6237
6238
  }
6239
+ function isInputHidden(element) {
6240
+ return element.is("input") && element.getAttributeValue("type") === "hidden";
6241
+ }
6242
+ function isInputCheckbox(element) {
6243
+ return element.is("input") && element.getAttributeValue("type") === "checkbox";
6244
+ }
6245
+ function isCheckboxWithDefault(control, previous, options) {
6246
+ const { allowCheckboxDefault } = options;
6247
+ if (!allowCheckboxDefault) {
6248
+ return false;
6249
+ }
6250
+ if (!previous.potentialHiddenDefault) {
6251
+ return false;
6252
+ }
6253
+ if (!isInputCheckbox(control)) {
6254
+ return false;
6255
+ }
6256
+ return true;
6257
+ }
6238
6258
  function getDocumentation(context) {
6239
6259
  const trailer = "Each form control must have a unique name.";
6240
6260
  const { name } = context;
@@ -6257,6 +6277,9 @@ class FormDupName extends Rule {
6257
6277
  allowArrayBrackets: {
6258
6278
  type: "boolean"
6259
6279
  },
6280
+ allowCheckboxDefault: {
6281
+ type: "boolean"
6282
+ },
6260
6283
  shared: {
6261
6284
  type: "array",
6262
6285
  items: {
@@ -6322,14 +6345,20 @@ class FormDupName extends Rule {
6322
6345
  }
6323
6346
  if (!details && isarray) {
6324
6347
  elements.set(basename, {
6325
- array: true
6348
+ array: true,
6349
+ potentialHiddenDefault: false
6326
6350
  });
6327
6351
  }
6328
6352
  if (isarray) {
6329
6353
  return;
6330
6354
  }
6331
6355
  }
6332
- if (elements.has(name)) {
6356
+ const previous = elements.get(name);
6357
+ if (previous) {
6358
+ if (isCheckboxWithDefault(control, previous, this.options)) {
6359
+ previous.potentialHiddenDefault = false;
6360
+ return;
6361
+ }
6333
6362
  const context = {
6334
6363
  name,
6335
6364
  kind: "duplicate"
@@ -6342,7 +6371,8 @@ class FormDupName extends Rule {
6342
6371
  });
6343
6372
  } else {
6344
6373
  elements.set(name, {
6345
- array: false
6374
+ array: false,
6375
+ potentialHiddenDefault: isInputHidden(control)
6346
6376
  });
6347
6377
  }
6348
6378
  }
@@ -7196,20 +7226,23 @@ class MultipleLabeledControls extends Rule {
7196
7226
  }
7197
7227
  setup() {
7198
7228
  this.labelable = this.getTagsWithProperty("labelable").join(",");
7199
- this.on("element:ready", (event) => {
7200
- const { target } = event;
7201
- if (target.tagName !== "label") {
7202
- return;
7203
- }
7204
- const numControls = this.getNumLabledControls(target);
7205
- if (numControls <= 1) {
7206
- return;
7229
+ this.on("dom:ready", (event) => {
7230
+ const { document } = event;
7231
+ const labels = document.querySelectorAll("label");
7232
+ for (const label of labels) {
7233
+ const numControls = this.getNumLabledControls(label);
7234
+ if (numControls <= 1) {
7235
+ continue;
7236
+ }
7237
+ this.report(label, "<label> is associated with multiple controls", label.location);
7207
7238
  }
7208
- this.report(target, "<label> is associated with multiple controls", target.location);
7209
7239
  });
7210
7240
  }
7211
7241
  getNumLabledControls(src) {
7212
- const controls = src.querySelectorAll(this.labelable).map((node) => node.id);
7242
+ const controls = src.querySelectorAll(this.labelable).filter((node) => {
7243
+ var _a;
7244
+ return (_a = node.meta) == null ? void 0 : _a.labelable;
7245
+ }).map((node) => node.id);
7213
7246
  const attr = src.getAttribute("for");
7214
7247
  if (!attr || attr.isDynamic || !attr.value) {
7215
7248
  return controls.length;
@@ -9753,7 +9786,7 @@ class ValidID extends Rule {
9753
9786
  const message = this.messages[context].replace("id", "ID").replace(/^(.)/, (m) => m.toUpperCase());
9754
9787
  const relaxedDescription = relaxed ? [] : [
9755
9788
  " - ID must begin with a letter",
9756
- " - ID must only contain alphanumerical characters, `-` and `_`"
9789
+ " - ID must only contain letters, digits, `-` and `_`"
9757
9790
  ];
9758
9791
  return {
9759
9792
  description: [
@@ -9788,12 +9821,12 @@ class ValidID extends Rule {
9788
9821
  if (relaxed) {
9789
9822
  return;
9790
9823
  }
9791
- if (value.match(/^[^a-zA-Z]/)) {
9824
+ if (value.match(/^[^\p{L}]/u)) {
9792
9825
  const context = 3 /* LEADING_CHARACTER */;
9793
9826
  this.report(event.target, this.messages[context], event.valueLocation, context);
9794
9827
  return;
9795
9828
  }
9796
- if (value.match(/[^a-zA-Z0-9-_]/)) {
9829
+ if (value.match(/[^\p{L}\p{N}_-]/u)) {
9797
9830
  const context = 4 /* DISALLOWED_CHARACTER */;
9798
9831
  this.report(event.target, this.messages[context], event.valueLocation, context);
9799
9832
  }
@@ -9804,7 +9837,7 @@ class ValidID extends Rule {
9804
9837
  [1 /* EMPTY */]: "element id must not be empty",
9805
9838
  [2 /* WHITESPACE */]: "element id must not contain whitespace",
9806
9839
  [3 /* LEADING_CHARACTER */]: "element id must begin with a letter",
9807
- [4 /* DISALLOWED_CHARACTER */]: "element id must only contain alphanumerical, dash and underscore characters"
9840
+ [4 /* DISALLOWED_CHARACTER */]: "element id must only contain letters, digits, dash and underscore characters"
9808
9841
  };
9809
9842
  }
9810
9843
  isRelevant(event) {
@@ -9931,7 +9964,7 @@ function styleDescription(style) {
9931
9964
  class H30 extends Rule {
9932
9965
  documentation() {
9933
9966
  return {
9934
- description: "WCAG 2.1 requires each `<a>` anchor link to have a text describing the purpose of the link using either plain text or an `<img>` with the `alt` attribute set.",
9967
+ description: "WCAG 2.1 requires each `<a href>` anchor link to have a text describing the purpose of the link using either plain text or an `<img>` with the `alt` attribute set.",
9935
9968
  url: "https://html-validate.org/rules/wcag/h30.html"
9936
9969
  };
9937
9970
  }
@@ -9939,6 +9972,9 @@ class H30 extends Rule {
9939
9972
  this.on("dom:ready", (event) => {
9940
9973
  const links = event.document.getElementsByTagName("a");
9941
9974
  for (const link of links) {
9975
+ if (!link.hasAttribute("href")) {
9976
+ continue;
9977
+ }
9942
9978
  if (!inAccessibilityTree(link)) {
9943
9979
  continue;
9944
9980
  }
@@ -10009,7 +10045,10 @@ function hasAssociatedSubmit(document, form) {
10009
10045
  class H36 extends Rule {
10010
10046
  documentation() {
10011
10047
  return {
10012
- description: 'WCAG 2.1 requires all images used as submit buttons to have a textual description using the alt attribute. The alt text cannot be empty (`alt=""`).',
10048
+ description: [
10049
+ "WCAG 2.1 requires all images used as submit buttons to have a non-empty textual description using the `alt` attribute.",
10050
+ 'The alt text cannot be empty (`alt=""`).'
10051
+ ].join("\n"),
10013
10052
  url: "https://html-validate.org/rules/wcag/h36.html"
10014
10053
  };
10015
10054
  }
@@ -10021,8 +10060,17 @@ class H36 extends Rule {
10021
10060
  if (node.getAttributeValue("type") !== "image") {
10022
10061
  return;
10023
10062
  }
10063
+ if (!inAccessibilityTree(node)) {
10064
+ return;
10065
+ }
10024
10066
  if (!hasAltText(node)) {
10025
- this.report(node, "image used as submit button must have alt text");
10067
+ const message = "image used as submit button must have non-empty alt text";
10068
+ const alt = node.getAttribute("alt");
10069
+ this.report({
10070
+ node,
10071
+ message,
10072
+ location: alt ? alt.keyLocation : node.location
10073
+ });
10026
10074
  }
10027
10075
  });
10028
10076
  }
@@ -10032,19 +10080,6 @@ const defaults$1 = {
10032
10080
  allowEmpty: true,
10033
10081
  alias: []
10034
10082
  };
10035
- function needsAlt(node) {
10036
- if (node.is("img")) {
10037
- return true;
10038
- }
10039
- if (node.is("input") && node.getAttributeValue("type") === "image") {
10040
- return true;
10041
- }
10042
- return false;
10043
- }
10044
- function getTag(node) {
10045
- return node.is("input") ? `<input type="${/* istanbul ignore next */
10046
- node.getAttributeValue("type") ?? ""}">` : `<${node.tagName}>`;
10047
- }
10048
10083
  class H37 extends Rule {
10049
10084
  constructor(options) {
10050
10085
  super({ ...defaults$1, ...options });
@@ -10081,16 +10116,13 @@ class H37 extends Rule {
10081
10116
  setup() {
10082
10117
  this.on("dom:ready", (event) => {
10083
10118
  const { document } = event;
10084
- const nodes = document.querySelectorAll("img, input");
10119
+ const nodes = document.querySelectorAll("img");
10085
10120
  for (const node of nodes) {
10086
10121
  this.validateNode(node);
10087
10122
  }
10088
10123
  });
10089
10124
  }
10090
10125
  validateNode(node) {
10091
- if (!needsAlt(node)) {
10092
- return;
10093
- }
10094
10126
  if (!inAccessibilityTree(node)) {
10095
10127
  return;
10096
10128
  }
@@ -10102,11 +10134,12 @@ class H37 extends Rule {
10102
10134
  return;
10103
10135
  }
10104
10136
  }
10137
+ const tag = node.annotatedName;
10105
10138
  if (node.hasAttribute("alt")) {
10106
10139
  const attr = node.getAttribute("alt");
10107
- this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
10140
+ this.report(node, `${tag} cannot have empty "alt" attribute`, attr.keyLocation);
10108
10141
  } else {
10109
- this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
10142
+ this.report(node, `${tag} is missing required "alt" attribute`, node.location);
10110
10143
  }
10111
10144
  }
10112
10145
  }
@@ -10207,7 +10240,6 @@ const bundledRules$1 = {
10207
10240
  "wcag/h67": H67,
10208
10241
  "wcag/h71": H71
10209
10242
  };
10210
- var WCAG = bundledRules$1;
10211
10243
 
10212
10244
  const bundledRules = {
10213
10245
  "allowed-links": AllowedLinks,
@@ -10293,7 +10325,7 @@ const bundledRules = {
10293
10325
  "valid-id": ValidID,
10294
10326
  "void-content": VoidContent,
10295
10327
  "void-style": VoidStyle,
10296
- ...WCAG
10328
+ ...bundledRules$1
10297
10329
  };
10298
10330
 
10299
10331
  var defaultConfig = {};
@@ -10330,7 +10362,6 @@ const config$4 = {
10330
10362
  "wcag/h71": "error"
10331
10363
  }
10332
10364
  };
10333
- var a11y = config$4;
10334
10365
 
10335
10366
  const config$3 = {
10336
10367
  rules: {
@@ -10341,7 +10372,6 @@ const config$3 = {
10341
10372
  "require-sri": "error"
10342
10373
  }
10343
10374
  };
10344
- var document = config$3;
10345
10375
 
10346
10376
  const config$2 = {
10347
10377
  rules: {
@@ -10350,7 +10380,6 @@ const config$2 = {
10350
10380
  "void-style": "off"
10351
10381
  }
10352
10382
  };
10353
- var prettier = config$2;
10354
10383
 
10355
10384
  const config$1 = {
10356
10385
  rules: {
@@ -10434,7 +10463,6 @@ const config$1 = {
10434
10463
  "wcag/h71": "error"
10435
10464
  }
10436
10465
  };
10437
- var recommended = config$1;
10438
10466
 
10439
10467
  const config = {
10440
10468
  rules: {
@@ -10473,18 +10501,17 @@ const config = {
10473
10501
  "void-content": "error"
10474
10502
  }
10475
10503
  };
10476
- var standard = config;
10477
10504
 
10478
10505
  const presets = {
10479
- "html-validate:a11y": a11y,
10480
- "html-validate:document": document,
10481
- "html-validate:prettier": prettier,
10482
- "html-validate:recommended": recommended,
10483
- "html-validate:standard": standard,
10506
+ "html-validate:a11y": config$4,
10507
+ "html-validate:document": config$3,
10508
+ "html-validate:prettier": config$2,
10509
+ "html-validate:recommended": config$1,
10510
+ "html-validate:standard": config,
10484
10511
  /* @deprecated aliases */
10485
- "htmlvalidate:recommended": recommended,
10486
- "htmlvalidate:document": document,
10487
- "html-validate:a17y": a11y
10512
+ "htmlvalidate:recommended": config$1,
10513
+ "htmlvalidate:document": config$3,
10514
+ "html-validate:a17y": config$4
10488
10515
  };
10489
10516
  var Presets = presets;
10490
10517
 
@@ -12699,7 +12726,7 @@ class HtmlValidate {
12699
12726
  }
12700
12727
 
12701
12728
  const name = "html-validate";
12702
- const version = "8.18.2";
12729
+ const version = "8.19.1";
12703
12730
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12704
12731
 
12705
12732
  function definePlugin(plugin) {
@@ -13385,7 +13412,6 @@ function checkstyleFormatter(results) {
13385
13412
  return output;
13386
13413
  }
13387
13414
  const formatter$3 = checkstyleFormatter;
13388
- var checkstyle = formatter$3;
13389
13415
 
13390
13416
  const defaults = {
13391
13417
  showLink: true,
@@ -13491,7 +13517,6 @@ function jsonFormatter(results) {
13491
13517
  return JSON.stringify(results);
13492
13518
  }
13493
13519
  const formatter$2 = jsonFormatter;
13494
- var json = formatter$2;
13495
13520
 
13496
13521
  function linkSummary(results) {
13497
13522
  const urls = results.reduce((result, it) => {
@@ -13510,7 +13535,7 @@ ${lines.join("")}
13510
13535
  `;
13511
13536
  }
13512
13537
  function stylish(results) {
13513
- const errors = stylish$2.stylish(
13538
+ const errors = stylish$1.stylish(
13514
13539
  results.map((it) => ({
13515
13540
  ...it,
13516
13541
  fixableErrorCount: 0,
@@ -13521,7 +13546,6 @@ function stylish(results) {
13521
13546
  return `${errors}${links}`;
13522
13547
  }
13523
13548
  const formatter$1 = stylish;
13524
- var stylish$1 = formatter$1;
13525
13549
 
13526
13550
  function textFormatter(results) {
13527
13551
  let output = "";
@@ -13549,14 +13573,13 @@ function textFormatter(results) {
13549
13573
  return total > 0 ? output : "";
13550
13574
  }
13551
13575
  const formatter = textFormatter;
13552
- var text = formatter;
13553
13576
 
13554
13577
  const availableFormatters = {
13555
- checkstyle,
13578
+ checkstyle: formatter$3,
13556
13579
  codeframe,
13557
- json,
13558
- stylish: stylish$1,
13559
- text
13580
+ json: formatter$2,
13581
+ stylish: formatter$1,
13582
+ text: formatter
13560
13583
  };
13561
13584
  function getFormatter(name) {
13562
13585
  return availableFormatters[name] ?? null;