html-validate 7.8.0 → 7.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.
package/dist/es/core.js CHANGED
@@ -5,10 +5,10 @@ import Ajv from 'ajv';
5
5
  import deepmerge from 'deepmerge';
6
6
  import * as espree from 'espree';
7
7
  import * as walk from 'acorn-walk';
8
+ import { e as entities$1, h as html5, b as bundledElements } from './elements.js';
8
9
  import path from 'path';
9
10
  import semver from 'semver';
10
11
  import kleur from 'kleur';
11
- import { e as entities$1, b as bundledElements } from './elements.js';
12
12
  import { createRequire } from 'module';
13
13
  import { codeFrameColumns } from '@babel/code-frame';
14
14
  import stylishImpl from '@html-validate/stylish';
@@ -3533,7 +3533,7 @@ class Rule {
3533
3533
  }
3534
3534
  }
3535
3535
 
3536
- const defaults$t = {
3536
+ const defaults$u = {
3537
3537
  allowExternal: true,
3538
3538
  allowRelative: true,
3539
3539
  allowAbsolute: true,
@@ -3577,7 +3577,7 @@ function matchList(value, list) {
3577
3577
  }
3578
3578
  class AllowedLinks extends Rule {
3579
3579
  constructor(options) {
3580
- super({ ...defaults$t, ...options });
3580
+ super({ ...defaults$u, ...options });
3581
3581
  this.allowExternal = parseAllow(this.options.allowExternal);
3582
3582
  this.allowRelative = parseAllow(this.options.allowRelative);
3583
3583
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3725,7 +3725,7 @@ var RuleContext$1;
3725
3725
  RuleContext["MISSING_ALT"] = "missing-alt";
3726
3726
  RuleContext["MISSING_HREF"] = "missing-href";
3727
3727
  })(RuleContext$1 || (RuleContext$1 = {}));
3728
- const defaults$s = {
3728
+ const defaults$t = {
3729
3729
  accessible: true,
3730
3730
  };
3731
3731
  function findByTarget(target, siblings) {
@@ -3734,7 +3734,7 @@ function findByTarget(target, siblings) {
3734
3734
  function getAltText(node) {
3735
3735
  return node.getAttributeValue("alt");
3736
3736
  }
3737
- function getDescription(context) {
3737
+ function getDescription$1(context) {
3738
3738
  switch (context) {
3739
3739
  case RuleContext$1.MISSING_ALT:
3740
3740
  return [
@@ -3763,7 +3763,7 @@ function getDescription(context) {
3763
3763
  }
3764
3764
  class AreaAlt extends Rule {
3765
3765
  constructor(options) {
3766
- super({ ...defaults$s, ...options });
3766
+ super({ ...defaults$t, ...options });
3767
3767
  }
3768
3768
  static schema() {
3769
3769
  return {
@@ -3774,7 +3774,7 @@ class AreaAlt extends Rule {
3774
3774
  }
3775
3775
  documentation(context) {
3776
3776
  return {
3777
- description: getDescription(context).join("\n"),
3777
+ description: getDescription$1(context).join("\n"),
3778
3778
  url: "https://html-validate.org/rules/area-alt.html",
3779
3779
  };
3780
3780
  }
@@ -3927,13 +3927,13 @@ class AriaLabelMisuse extends Rule {
3927
3927
  class ConfigError extends UserError {
3928
3928
  }
3929
3929
 
3930
- const defaults$r = {
3930
+ const defaults$s = {
3931
3931
  style: "lowercase",
3932
3932
  ignoreForeign: true,
3933
3933
  };
3934
3934
  class AttrCase extends Rule {
3935
3935
  constructor(options) {
3936
- super({ ...defaults$r, ...options });
3936
+ super({ ...defaults$s, ...options });
3937
3937
  this.style = new CaseStyle(this.options.style, "attr-case");
3938
3938
  }
3939
3939
  static schema() {
@@ -4278,7 +4278,7 @@ class AttrDelimiter extends Rule {
4278
4278
  }
4279
4279
 
4280
4280
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4281
- const defaults$q = {
4281
+ const defaults$r = {
4282
4282
  pattern: DEFAULT_PATTERN,
4283
4283
  ignoreForeign: true,
4284
4284
  };
@@ -4315,7 +4315,7 @@ function generateDescription(name, pattern) {
4315
4315
  }
4316
4316
  class AttrPattern extends Rule {
4317
4317
  constructor(options) {
4318
- super({ ...defaults$q, ...options });
4318
+ super({ ...defaults$r, ...options });
4319
4319
  this.pattern = generateRegexp(this.options.pattern);
4320
4320
  }
4321
4321
  static schema() {
@@ -4376,7 +4376,7 @@ var QuoteStyle;
4376
4376
  QuoteStyle["AUTO_QUOTE"] = "auto";
4377
4377
  QuoteStyle["ANY_QUOTE"] = "any";
4378
4378
  })(QuoteStyle || (QuoteStyle = {}));
4379
- const defaults$p = {
4379
+ const defaults$q = {
4380
4380
  style: "auto",
4381
4381
  unquoted: false,
4382
4382
  };
@@ -4416,10 +4416,6 @@ function describeStyle(style, unquoted) {
4416
4416
  return `${description.join(" or\n")}\n`;
4417
4417
  }
4418
4418
  class AttrQuotes extends Rule {
4419
- constructor(options) {
4420
- super({ ...defaults$p, ...options });
4421
- this.style = parseStyle$4(this.options.style);
4422
- }
4423
4419
  static schema() {
4424
4420
  return {
4425
4421
  style: {
@@ -4446,6 +4442,10 @@ class AttrQuotes extends Rule {
4446
4442
  url: "https://html-validate.org/rules/attr-quotes.html",
4447
4443
  };
4448
4444
  }
4445
+ constructor(options) {
4446
+ super({ ...defaults$q, ...options });
4447
+ this.style = parseStyle$4(this.options.style);
4448
+ }
4449
4449
  setup() {
4450
4450
  this.on("attr", (event) => {
4451
4451
  /* ignore attributes with no value */
@@ -4613,12 +4613,12 @@ class AttributeAllowedValues extends Rule {
4613
4613
  }
4614
4614
  }
4615
4615
 
4616
- const defaults$o = {
4616
+ const defaults$p = {
4617
4617
  style: "omit",
4618
4618
  };
4619
4619
  class AttributeBooleanStyle extends Rule {
4620
4620
  constructor(options) {
4621
- super({ ...defaults$o, ...options });
4621
+ super({ ...defaults$p, ...options });
4622
4622
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4623
4623
  }
4624
4624
  static schema() {
@@ -4694,12 +4694,12 @@ function reportMessage$1(attr, style) {
4694
4694
  return "";
4695
4695
  }
4696
4696
 
4697
- const defaults$n = {
4697
+ const defaults$o = {
4698
4698
  style: "omit",
4699
4699
  };
4700
4700
  class AttributeEmptyStyle extends Rule {
4701
4701
  constructor(options) {
4702
- super({ ...defaults$n, ...options });
4702
+ super({ ...defaults$o, ...options });
4703
4703
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4704
4704
  }
4705
4705
  static schema() {
@@ -4855,12 +4855,12 @@ function describePattern(pattern) {
4855
4855
  }
4856
4856
  }
4857
4857
 
4858
- const defaults$m = {
4858
+ const defaults$n = {
4859
4859
  pattern: "kebabcase",
4860
4860
  };
4861
4861
  class ClassPattern extends Rule {
4862
4862
  constructor(options) {
4863
- super({ ...defaults$m, ...options });
4863
+ super({ ...defaults$n, ...options });
4864
4864
  this.pattern = parsePattern(this.options.pattern);
4865
4865
  }
4866
4866
  static schema() {
@@ -4969,13 +4969,13 @@ class CloseOrder extends Rule {
4969
4969
  }
4970
4970
  }
4971
4971
 
4972
- const defaults$l = {
4972
+ const defaults$m = {
4973
4973
  include: null,
4974
4974
  exclude: null,
4975
4975
  };
4976
4976
  class Deprecated extends Rule {
4977
4977
  constructor(options) {
4978
- super({ ...defaults$l, ...options });
4978
+ super({ ...defaults$m, ...options });
4979
4979
  }
4980
4980
  static schema() {
4981
4981
  return {
@@ -5138,12 +5138,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5138
5138
  }
5139
5139
  };
5140
5140
 
5141
- const defaults$k = {
5141
+ const defaults$l = {
5142
5142
  style: "uppercase",
5143
5143
  };
5144
5144
  class DoctypeStyle extends Rule {
5145
5145
  constructor(options) {
5146
- super({ ...defaults$k, ...options });
5146
+ super({ ...defaults$l, ...options });
5147
5147
  }
5148
5148
  static schema() {
5149
5149
  return {
@@ -5175,12 +5175,12 @@ class DoctypeStyle extends Rule {
5175
5175
  }
5176
5176
  }
5177
5177
 
5178
- const defaults$j = {
5178
+ const defaults$k = {
5179
5179
  style: "lowercase",
5180
5180
  };
5181
5181
  class ElementCase extends Rule {
5182
5182
  constructor(options) {
5183
- super({ ...defaults$j, ...options });
5183
+ super({ ...defaults$k, ...options });
5184
5184
  this.style = new CaseStyle(this.options.style, "element-case");
5185
5185
  }
5186
5186
  static schema() {
@@ -5246,14 +5246,14 @@ class ElementCase extends Rule {
5246
5246
  }
5247
5247
  }
5248
5248
 
5249
- const defaults$i = {
5249
+ const defaults$j = {
5250
5250
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5251
5251
  whitelist: [],
5252
5252
  blacklist: [],
5253
5253
  };
5254
5254
  class ElementName extends Rule {
5255
5255
  constructor(options) {
5256
- super({ ...defaults$i, ...options });
5256
+ super({ ...defaults$j, ...options });
5257
5257
  // eslint-disable-next-line security/detect-non-literal-regexp
5258
5258
  this.pattern = new RegExp(this.options.pattern);
5259
5259
  }
@@ -5294,7 +5294,7 @@ class ElementName extends Rule {
5294
5294
  ...context.blacklist.map((cur) => `- ${cur}`),
5295
5295
  ];
5296
5296
  }
5297
- if (context.pattern !== defaults$i.pattern) {
5297
+ if (context.pattern !== defaults$j.pattern) {
5298
5298
  return [
5299
5299
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5300
5300
  "",
@@ -5767,9 +5767,9 @@ function hasImgAltText(node) {
5767
5767
  else if (node.is("svg")) {
5768
5768
  return node.textContent.trim() !== "";
5769
5769
  }
5770
- else {
5771
- return false;
5772
- }
5770
+ /* istanbul ignore next -- querySelector(..) is only going to return the two
5771
+ * above tags but this serves as a sane default if above assumption changes */
5772
+ return false;
5773
5773
  }
5774
5774
  class EmptyHeading extends Rule {
5775
5775
  documentation() {
@@ -5793,7 +5793,7 @@ class EmptyHeading extends Rule {
5793
5793
  return;
5794
5794
  }
5795
5795
  }
5796
- switch (classifyNodeText(heading)) {
5796
+ switch (classifyNodeText(heading, { ignoreHiddenRoot: true })) {
5797
5797
  case TextClassification.DYNAMIC_TEXT:
5798
5798
  case TextClassification.STATIC_TEXT:
5799
5799
  /* have some text content, consider ok */
@@ -5842,7 +5842,7 @@ class EmptyTitle extends Rule {
5842
5842
  }
5843
5843
  }
5844
5844
 
5845
- const defaults$h = {
5845
+ const defaults$i = {
5846
5846
  allowMultipleH1: false,
5847
5847
  minInitialRank: "h1",
5848
5848
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5873,7 +5873,7 @@ function parseMaxInitial(value) {
5873
5873
  }
5874
5874
  class HeadingLevel extends Rule {
5875
5875
  constructor(options) {
5876
- super({ ...defaults$h, ...options });
5876
+ super({ ...defaults$i, ...options });
5877
5877
  this.stack = [];
5878
5878
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5879
5879
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -6031,12 +6031,12 @@ class HeadingLevel extends Rule {
6031
6031
  }
6032
6032
  }
6033
6033
 
6034
- const defaults$g = {
6034
+ const defaults$h = {
6035
6035
  pattern: "kebabcase",
6036
6036
  };
6037
6037
  class IdPattern extends Rule {
6038
6038
  constructor(options) {
6039
- super({ ...defaults$g, ...options });
6039
+ super({ ...defaults$h, ...options });
6040
6040
  this.pattern = parsePattern(this.options.pattern);
6041
6041
  }
6042
6042
  static schema() {
@@ -6318,12 +6318,12 @@ function findLabelByParent(el) {
6318
6318
  return [];
6319
6319
  }
6320
6320
 
6321
- const defaults$f = {
6321
+ const defaults$g = {
6322
6322
  maxlength: 70,
6323
6323
  };
6324
6324
  class LongTitle extends Rule {
6325
6325
  constructor(options) {
6326
- super({ ...defaults$f, ...options });
6326
+ super({ ...defaults$g, ...options });
6327
6327
  this.maxlength = this.options.maxlength;
6328
6328
  }
6329
6329
  static schema() {
@@ -6406,6 +6406,49 @@ function parseContent(text) {
6406
6406
  }
6407
6407
  }
6408
6408
 
6409
+ function getName(attr) {
6410
+ const name = attr.value;
6411
+ if (!name || name instanceof DynamicValue) {
6412
+ return null;
6413
+ }
6414
+ return name;
6415
+ }
6416
+ class MapDupName extends Rule {
6417
+ documentation() {
6418
+ return {
6419
+ description: "`<map>` must have a unique name, it cannot be the same name as another `<map>` element",
6420
+ url: "https://html-validate.org/rules/map-dup-name.html",
6421
+ };
6422
+ }
6423
+ setup() {
6424
+ this.on("dom:ready", (event) => {
6425
+ const { document } = event;
6426
+ const maps = document.querySelectorAll("map[name]");
6427
+ const names = new Set();
6428
+ for (const map of maps) {
6429
+ const attr = map.getAttribute("name");
6430
+ /* istanbul ignore next -- should not happen as querySelector matches
6431
+ * only the elements with the name attribute */
6432
+ if (!attr) {
6433
+ continue;
6434
+ }
6435
+ const name = getName(attr);
6436
+ if (!name) {
6437
+ continue;
6438
+ }
6439
+ if (names.has(name)) {
6440
+ this.report({
6441
+ node: map,
6442
+ message: `<map> name must be unique`,
6443
+ location: attr.keyLocation,
6444
+ });
6445
+ }
6446
+ names.add(name);
6447
+ }
6448
+ });
6449
+ }
6450
+ }
6451
+
6409
6452
  class MissingDoctype extends Rule {
6410
6453
  documentation() {
6411
6454
  return {
@@ -6470,13 +6513,13 @@ class MultipleLabeledControls extends Rule {
6470
6513
  }
6471
6514
  }
6472
6515
 
6473
- const defaults$e = {
6516
+ const defaults$f = {
6474
6517
  include: null,
6475
6518
  exclude: null,
6476
6519
  };
6477
6520
  class NoAutoplay extends Rule {
6478
6521
  constructor(options) {
6479
- super({ ...defaults$e, ...options });
6522
+ super({ ...defaults$f, ...options });
6480
6523
  }
6481
6524
  documentation(context) {
6482
6525
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6717,14 +6760,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6717
6760
  }
6718
6761
  }
6719
6762
 
6720
- const defaults$d = {
6763
+ const defaults$e = {
6721
6764
  include: null,
6722
6765
  exclude: null,
6723
6766
  allowedProperties: ["display"],
6724
6767
  };
6725
6768
  class NoInlineStyle extends Rule {
6726
6769
  constructor(options) {
6727
- super({ ...defaults$d, ...options });
6770
+ super({ ...defaults$e, ...options });
6728
6771
  }
6729
6772
  static schema() {
6730
6773
  return {
@@ -6926,7 +6969,7 @@ class NoMultipleMain extends Rule {
6926
6969
  }
6927
6970
  }
6928
6971
 
6929
- const defaults$c = {
6972
+ const defaults$d = {
6930
6973
  relaxed: false,
6931
6974
  };
6932
6975
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -6943,7 +6986,7 @@ const replacementTable = {
6943
6986
  };
6944
6987
  class NoRawCharacters extends Rule {
6945
6988
  constructor(options) {
6946
- super({ ...defaults$c, ...options });
6989
+ super({ ...defaults$d, ...options });
6947
6990
  this.relaxed = this.options.relaxed;
6948
6991
  }
6949
6992
  static schema() {
@@ -7121,13 +7164,13 @@ class NoRedundantRole extends Rule {
7121
7164
  }
7122
7165
 
7123
7166
  const xmlns = /^(.+):.+$/;
7124
- const defaults$b = {
7167
+ const defaults$c = {
7125
7168
  ignoreForeign: true,
7126
7169
  ignoreXML: true,
7127
7170
  };
7128
7171
  class NoSelfClosing extends Rule {
7129
7172
  constructor(options) {
7130
- super({ ...defaults$b, ...options });
7173
+ super({ ...defaults$c, ...options });
7131
7174
  }
7132
7175
  static schema() {
7133
7176
  return {
@@ -7260,13 +7303,13 @@ const replacement = {
7260
7303
  reset: '<button type="reset">',
7261
7304
  image: '<button type="button">',
7262
7305
  };
7263
- const defaults$a = {
7306
+ const defaults$b = {
7264
7307
  include: null,
7265
7308
  exclude: null,
7266
7309
  };
7267
7310
  class PreferButton extends Rule {
7268
7311
  constructor(options) {
7269
- super({ ...defaults$a, ...options });
7312
+ super({ ...defaults$b, ...options });
7270
7313
  }
7271
7314
  static schema() {
7272
7315
  return {
@@ -7341,7 +7384,7 @@ class PreferButton extends Rule {
7341
7384
  }
7342
7385
  }
7343
7386
 
7344
- const defaults$9 = {
7387
+ const defaults$a = {
7345
7388
  mapping: {
7346
7389
  article: "article",
7347
7390
  banner: "header",
@@ -7371,7 +7414,7 @@ const defaults$9 = {
7371
7414
  };
7372
7415
  class PreferNativeElement extends Rule {
7373
7416
  constructor(options) {
7374
- super({ ...defaults$9, ...options });
7417
+ super({ ...defaults$a, ...options });
7375
7418
  }
7376
7419
  static schema() {
7377
7420
  return {
@@ -7491,12 +7534,12 @@ class PreferTbody extends Rule {
7491
7534
  }
7492
7535
  }
7493
7536
 
7494
- const defaults$8 = {
7537
+ const defaults$9 = {
7495
7538
  tags: ["script", "style"],
7496
7539
  };
7497
7540
  class RequireCSPNonce extends Rule {
7498
7541
  constructor(options) {
7499
- super({ ...defaults$8, ...options });
7542
+ super({ ...defaults$9, ...options });
7500
7543
  }
7501
7544
  static schema() {
7502
7545
  return {
@@ -7547,7 +7590,7 @@ class RequireCSPNonce extends Rule {
7547
7590
  }
7548
7591
  }
7549
7592
 
7550
- const defaults$7 = {
7593
+ const defaults$8 = {
7551
7594
  target: "all",
7552
7595
  include: null,
7553
7596
  exclude: null,
@@ -7559,7 +7602,7 @@ const supportSri = {
7559
7602
  };
7560
7603
  class RequireSri extends Rule {
7561
7604
  constructor(options) {
7562
- super({ ...defaults$7, ...options });
7605
+ super({ ...defaults$8, ...options });
7563
7606
  this.target = this.options.target;
7564
7607
  }
7565
7608
  static schema() {
@@ -7721,7 +7764,7 @@ class SvgFocusable extends Rule {
7721
7764
  }
7722
7765
  }
7723
7766
 
7724
- const defaults$6 = {
7767
+ const defaults$7 = {
7725
7768
  characters: [
7726
7769
  { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
7727
7770
  { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" },
@@ -7764,7 +7807,7 @@ function matchAll(text, regexp) {
7764
7807
  }
7765
7808
  class TelNonBreaking extends Rule {
7766
7809
  constructor(options) {
7767
- super({ ...defaults$6, ...options });
7810
+ super({ ...defaults$7, ...options });
7768
7811
  this.regex = constructRegex(this.options.characters);
7769
7812
  }
7770
7813
  static schema() {
@@ -8052,11 +8095,63 @@ class TextContent extends Rule {
8052
8095
  }
8053
8096
  }
8054
8097
 
8055
- const regexp$1 = /&([a-z0-9]+|#x?[0-9a-f]+);/gi;
8098
+ const defaults$6 = {
8099
+ ignoreCase: false,
8100
+ requireSemicolon: true,
8101
+ };
8102
+ const regexp$1 = /&(?:[a-z0-9]+|#x?[0-9a-f]+)(;|[^a-z0-9]|$)/gi;
8103
+ const lowercaseEntities = entities$1.map((it) => it.toLowerCase());
8104
+ function isNumerical(entity) {
8105
+ return entity.startsWith("&#");
8106
+ }
8107
+ function getLocation(location, entity, match) {
8108
+ var _a;
8109
+ /* istanbul ignore next: never happens in practive */
8110
+ const index = (_a = match.index) !== null && _a !== void 0 ? _a : 0;
8111
+ return sliceLocation(location, index, index + entity.length);
8112
+ }
8113
+ function getDescription(context, options) {
8114
+ const url = "https://html.spec.whatwg.org/multipage/named-characters.html";
8115
+ let message;
8116
+ if (context) {
8117
+ if (context.terminated) {
8118
+ message = `Unrecognized character reference \`${context.entity}\`.`;
8119
+ }
8120
+ else {
8121
+ message = `Character reference \`${context.entity}\` must be terminated by a semicolon.`;
8122
+ }
8123
+ }
8124
+ else {
8125
+ message = `Unrecognized character reference.`;
8126
+ }
8127
+ return [
8128
+ message,
8129
+ `HTML5 defines a set of [valid character references](${url}) but this is not a valid one.`,
8130
+ "",
8131
+ "Ensure that:",
8132
+ "",
8133
+ "1. The character is one of the listed names.",
8134
+ ...(options.ignoreCase ? [] : ["1. The case is correct (names are case sensitive)."]),
8135
+ ...(options.requireSemicolon ? ["1. The name is terminated with a `;`."] : []),
8136
+ ].join("\n");
8137
+ }
8056
8138
  class UnknownCharReference extends Rule {
8139
+ constructor(options) {
8140
+ super({ ...defaults$6, ...options });
8141
+ }
8142
+ static schema() {
8143
+ return {
8144
+ ignoreCase: {
8145
+ type: "boolean",
8146
+ },
8147
+ requireSemicolon: {
8148
+ type: "boolean",
8149
+ },
8150
+ };
8151
+ }
8057
8152
  documentation(context) {
8058
8153
  return {
8059
- description: `HTML defines a set of valid character references but ${context || "this"} is not a valid one.`,
8154
+ description: getDescription(context, this.options),
8060
8155
  url: "https://html-validate.org/rules/unrecognized-char-ref.html",
8061
8156
  };
8062
8157
  }
@@ -8068,7 +8163,9 @@ class UnknownCharReference extends Rule {
8068
8163
  if (child.nodeType !== NodeType.TEXT_NODE) {
8069
8164
  continue;
8070
8165
  }
8071
- this.findCharacterReferences(child.textContent, child.location);
8166
+ this.findCharacterReferences(node, child.textContent, child.location, {
8167
+ isAttribute: false,
8168
+ });
8072
8169
  }
8073
8170
  });
8074
8171
  this.on("attr", (event) => {
@@ -8076,25 +8173,72 @@ class UnknownCharReference extends Rule {
8076
8173
  if (!event.value) {
8077
8174
  return;
8078
8175
  }
8079
- this.findCharacterReferences(event.value.toString(), event.valueLocation);
8176
+ this.findCharacterReferences(event.target, event.value.toString(), event.valueLocation, {
8177
+ isAttribute: true,
8178
+ });
8080
8179
  });
8081
8180
  }
8082
- findCharacterReferences(text, location) {
8181
+ get entities() {
8182
+ if (this.options.ignoreCase) {
8183
+ return lowercaseEntities;
8184
+ }
8185
+ else {
8186
+ return entities$1;
8187
+ }
8188
+ }
8189
+ /* eslint-disable-next-line complexity */
8190
+ findCharacterReferences(node, text, location, { isAttribute }) {
8191
+ const { requireSemicolon } = this.options;
8192
+ const isQuerystring = isAttribute && text.includes("?");
8193
+ for (const { match, entity, raw, terminated } of this.getMatches(text)) {
8194
+ /* assume numeric entities are valid for now */
8195
+ if (isNumerical(entity)) {
8196
+ continue;
8197
+ }
8198
+ /* special case: when attributes use query parameters we skip checking
8199
+ * unterminated attributes */
8200
+ if (isQuerystring && !terminated) {
8201
+ continue;
8202
+ }
8203
+ const found = this.entities.includes(entity);
8204
+ /* ignore if this is a known character reference name */
8205
+ if (found && (terminated || !requireSemicolon)) {
8206
+ continue;
8207
+ }
8208
+ if (found && !terminated) {
8209
+ const entityLocation = getLocation(location, entity, match);
8210
+ const message = `Character reference "{{ entity }}" must be terminated by a semicolon`;
8211
+ const context = {
8212
+ entity: raw,
8213
+ terminated: false,
8214
+ };
8215
+ this.report(node, message, entityLocation, context);
8216
+ continue;
8217
+ }
8218
+ const entityLocation = getLocation(location, entity, match);
8219
+ const message = `Unrecognized character reference "{{ entity }}"`;
8220
+ const context = {
8221
+ entity: raw,
8222
+ terminated: true,
8223
+ };
8224
+ this.report(node, message, entityLocation, context);
8225
+ }
8226
+ }
8227
+ *getMatches(text) {
8083
8228
  let match;
8084
8229
  do {
8085
8230
  match = regexp$1.exec(text);
8086
8231
  if (match) {
8087
- const entity = match[0];
8088
- /* assume numeric entities are valid for now */
8089
- if (entity.startsWith("&#")) {
8090
- continue;
8232
+ const terminator = match[1]; // === ";" ? match[1] : "";
8233
+ const terminated = terminator === ";";
8234
+ const needSlice = terminator !== ";" && terminator.length > 0;
8235
+ const entity = needSlice ? match[0].slice(0, -1) : match[0];
8236
+ if (this.options.ignoreCase) {
8237
+ yield { match, entity: entity.toLowerCase(), raw: entity, terminated };
8091
8238
  }
8092
- /* ignore if this is a known character reference name */
8093
- if (entities$1.includes(entity)) {
8094
- continue;
8239
+ else {
8240
+ yield { match, entity, raw: entity, terminated };
8095
8241
  }
8096
- const entityLocation = sliceLocation(location, match.index, match.index + entity.length);
8097
- this.report(null, `Unrecognized character reference "${entity}"`, entityLocation, entity);
8098
8242
  }
8099
8243
  } while (match);
8100
8244
  }
@@ -8199,10 +8343,6 @@ const defaults$4 = {
8199
8343
  style: "omit",
8200
8344
  };
8201
8345
  class Void extends Rule {
8202
- constructor(options) {
8203
- super({ ...defaults$4, ...options });
8204
- this.style = parseStyle$1(this.options.style);
8205
- }
8206
8346
  get deprecated() {
8207
8347
  return true;
8208
8348
  }
@@ -8220,6 +8360,10 @@ class Void extends Rule {
8220
8360
  url: "https://html-validate.org/rules/void.html",
8221
8361
  };
8222
8362
  }
8363
+ constructor(options) {
8364
+ super({ ...defaults$4, ...options });
8365
+ this.style = parseStyle$1(this.options.style);
8366
+ }
8223
8367
  setup() {
8224
8368
  this.on("tag:end", (event) => {
8225
8369
  const current = event.target; // The current element being closed
@@ -8402,7 +8546,7 @@ class H30 extends Rule {
8402
8546
  continue;
8403
8547
  }
8404
8548
  /* check if text content is present (or dynamic) */
8405
- const textClassification = classifyNodeText(link);
8549
+ const textClassification = classifyNodeText(link, { ignoreHiddenRoot: true });
8406
8550
  if (textClassification !== TextClassification.EMPTY_TEXT) {
8407
8551
  continue;
8408
8552
  }
@@ -8590,6 +8734,32 @@ class H37 extends Rule {
8590
8734
  }
8591
8735
  }
8592
8736
 
8737
+ class H63 extends Rule {
8738
+ documentation() {
8739
+ return {
8740
+ description: "H63: Using the scope attribute to associate header cells and data cells in data tables",
8741
+ url: "https://html-validate.org/rules/wcag/h63.html",
8742
+ };
8743
+ }
8744
+ setup() {
8745
+ this.on("tag:ready", (event) => {
8746
+ var _a, _b, _c, _d;
8747
+ const node = event.target;
8748
+ /* only validate th */
8749
+ if (!node || node.tagName !== "th") {
8750
+ return;
8751
+ }
8752
+ /* ignore elements with valid scope values */
8753
+ const scope = node.getAttributeValue("scope");
8754
+ const scopeMeta = (_b = (_a = html5 === null || html5 === void 0 ? void 0 : html5.th) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b.scope;
8755
+ if (scope && ((_c = scopeMeta.enum) === null || _c === void 0 ? void 0 : _c.includes(scope))) {
8756
+ return;
8757
+ }
8758
+ this.report(node, `<th> element must have a valid scope attribute: ${((_d = scopeMeta.enum) !== null && _d !== void 0 ? _d : []).join(", ")}`, node.location);
8759
+ });
8760
+ }
8761
+ }
8762
+
8593
8763
  class H67 extends Rule {
8594
8764
  documentation() {
8595
8765
  return {
@@ -8654,6 +8824,7 @@ const bundledRules$1 = {
8654
8824
  "wcag/h32": H32,
8655
8825
  "wcag/h36": H36,
8656
8826
  "wcag/h37": H37,
8827
+ "wcag/h63": H63,
8657
8828
  "wcag/h67": H67,
8658
8829
  "wcag/h71": H71,
8659
8830
  };
@@ -8695,6 +8866,7 @@ const bundledRules = {
8695
8866
  "input-attributes": InputAttributes,
8696
8867
  "input-missing-label": InputMissingLabel,
8697
8868
  "long-title": LongTitle,
8869
+ "map-dup-name": MapDupName,
8698
8870
  "meta-refresh": MetaRefresh,
8699
8871
  "missing-doctype": MissingDoctype,
8700
8872
  "multiple-labeled-controls": MultipleLabeledControls,
@@ -8757,6 +8929,7 @@ const config$3 = {
8757
8929
  "wcag/h32": "error",
8758
8930
  "wcag/h36": "error",
8759
8931
  "wcag/h37": "error",
8932
+ "wcag/h63": "error",
8760
8933
  "wcag/h67": "error",
8761
8934
  "wcag/h71": "error",
8762
8935
  },
@@ -8804,6 +8977,7 @@ const config$1 = {
8804
8977
  "empty-title": "error",
8805
8978
  "input-attributes": "error",
8806
8979
  "long-title": "error",
8980
+ "map-dup-name": "error",
8807
8981
  "meta-refresh": "error",
8808
8982
  "multiple-labeled-controls": "error",
8809
8983
  "no-autoplay": ["error", { include: ["audio", "video"] }],
@@ -8838,6 +9012,7 @@ const config$1 = {
8838
9012
  "wcag/h32": "error",
8839
9013
  "wcag/h36": "error",
8840
9014
  "wcag/h37": "error",
9015
+ "wcag/h63": "error",
8841
9016
  "wcag/h67": "error",
8842
9017
  "wcag/h71": "error",
8843
9018
  },
@@ -8862,6 +9037,7 @@ const config = {
8862
9037
  "element-required-ancestor": "error",
8863
9038
  "element-required-attributes": "error",
8864
9039
  "element-required-content": "error",
9040
+ "map-dup-name": "error",
8865
9041
  "multiple-labeled-controls": "error",
8866
9042
  "no-deprecated-attr": "error",
8867
9043
  "no-dup-attr": "error",
@@ -9038,38 +9214,6 @@ function configDataFromFile(filename) {
9038
9214
  * @public
9039
9215
  */
9040
9216
  class Config {
9041
- /**
9042
- * @internal
9043
- */
9044
- constructor(options) {
9045
- var _a;
9046
- this.transformers = [];
9047
- const initial = {
9048
- extends: [],
9049
- plugins: [],
9050
- rules: {},
9051
- transform: {},
9052
- };
9053
- this.config = mergeInternal(initial, options || {});
9054
- this.metaTable = null;
9055
- this.rootDir = this.findRootDir();
9056
- this.initialized = false;
9057
- /* load plugins */
9058
- this.plugins = this.loadPlugins(this.config.plugins || []);
9059
- this.configurations = this.loadConfigurations(this.plugins);
9060
- this.extendMeta(this.plugins);
9061
- /* process extended configs */
9062
- this.config = this.extendConfig((_a = this.config.extends) !== null && _a !== void 0 ? _a : []);
9063
- /* reset extends as we already processed them, this prevents the next config
9064
- * from reapplying config from extended config as well as duplicate entries
9065
- * when merging arrays */
9066
- this.config.extends = [];
9067
- /* rules explicitly set by passed options should have precedence over any
9068
- * extended rules, not the other way around. */
9069
- if (options && options.rules) {
9070
- this.config = mergeInternal(this.config, { rules: options.rules });
9071
- }
9072
- }
9073
9217
  /**
9074
9218
  * Create a new blank configuration. See also `Config.defaultConfig()`.
9075
9219
  */
@@ -9131,6 +9275,38 @@ class Config {
9131
9275
  static defaultConfig() {
9132
9276
  return new Config(defaultConfig);
9133
9277
  }
9278
+ /**
9279
+ * @internal
9280
+ */
9281
+ constructor(options) {
9282
+ var _a;
9283
+ this.transformers = [];
9284
+ const initial = {
9285
+ extends: [],
9286
+ plugins: [],
9287
+ rules: {},
9288
+ transform: {},
9289
+ };
9290
+ this.config = mergeInternal(initial, options || {});
9291
+ this.metaTable = null;
9292
+ this.rootDir = this.findRootDir();
9293
+ this.initialized = false;
9294
+ /* load plugins */
9295
+ this.plugins = this.loadPlugins(this.config.plugins || []);
9296
+ this.configurations = this.loadConfigurations(this.plugins);
9297
+ this.extendMeta(this.plugins);
9298
+ /* process extended configs */
9299
+ this.config = this.extendConfig((_a = this.config.extends) !== null && _a !== void 0 ? _a : []);
9300
+ /* reset extends as we already processed them, this prevents the next config
9301
+ * from reapplying config from extended config as well as duplicate entries
9302
+ * when merging arrays */
9303
+ this.config.extends = [];
9304
+ /* rules explicitly set by passed options should have precedence over any
9305
+ * extended rules, not the other way around. */
9306
+ if (options && options.rules) {
9307
+ this.config = mergeInternal(this.config, { rules: options.rules });
9308
+ }
9309
+ }
9134
9310
  /**
9135
9311
  * Initialize plugins, transforms etc.
9136
9312
  *
@@ -10950,10 +11126,19 @@ class HtmlValidate {
10950
11126
  /** @public */
10951
11127
  const name = "html-validate";
10952
11128
  /** @public */
10953
- const version = "7.8.0";
11129
+ const version = "7.10.0";
10954
11130
  /** @public */
10955
11131
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
10956
11132
 
11133
+ /**
11134
+ * Helper function to assist IDE with completion and type-checking.
11135
+ *
11136
+ * @public
11137
+ */
11138
+ function definePlugin(plugin) {
11139
+ return plugin;
11140
+ }
11141
+
10957
11142
  const defaults$1 = {
10958
11143
  silent: false,
10959
11144
  version,
@@ -11384,5 +11569,5 @@ function getFormatter(name) {
11384
11569
  return (_a = availableFormatters[name]) !== null && _a !== void 0 ? _a : null;
11385
11570
  }
11386
11571
 
11387
- export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, Reporter as h, TemplateExtractor as i, getFormatter as j, ensureError as k, legacyRequire as l, configDataFromFile as m, compatibilityCheck as n, codeframe as o, presets as p, isTextNode as q, ruleExists as r, sliceLocation as s, isElementNode as t, generateIdSelector as u, version as v, name as w, bugs as x };
11572
+ export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, Reporter as h, TemplateExtractor as i, definePlugin as j, getFormatter as k, legacyRequire as l, ensureError as m, configDataFromFile as n, compatibilityCheck as o, presets as p, codeframe as q, ruleExists as r, sliceLocation as s, isTextNode as t, isElementNode as u, version as v, generateIdSelector as w, name as x, bugs as y };
11388
11573
  //# sourceMappingURL=core.js.map