html-validate 10.5.0 → 10.7.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/esm/core.js CHANGED
@@ -1207,7 +1207,9 @@ class MetaTable {
1207
1207
  );
1208
1208
  }
1209
1209
  for (const [key, value] of Object.entries(obj)) {
1210
- if (key === "$schema") continue;
1210
+ if (key === "$schema") {
1211
+ continue;
1212
+ }
1211
1213
  this.addEntry(key, migrateElement(value));
1212
1214
  }
1213
1215
  } catch (err) {
@@ -1307,7 +1309,9 @@ class MetaTable {
1307
1309
  * global, e.g. to assign global attributes.
1308
1310
  */
1309
1311
  resolveGlobal() {
1310
- if (!this.elements["*"]) return;
1312
+ if (!this.elements["*"]) {
1313
+ return;
1314
+ }
1311
1315
  const global = this.elements["*"];
1312
1316
  delete this.elements["*"];
1313
1317
  delete global.tagName;
@@ -1481,7 +1485,9 @@ function sliceSize(size, begin, end) {
1481
1485
  return Math.min(size, end - begin);
1482
1486
  }
1483
1487
  function sliceLocation(location, begin, end, wrap) {
1484
- if (!location) return null;
1488
+ if (!location) {
1489
+ return null;
1490
+ }
1485
1491
  const size = sliceSize(location.size, begin, end);
1486
1492
  const sliced = {
1487
1493
  filename: location.filename,
@@ -2360,7 +2366,7 @@ class Selector {
2360
2366
 
2361
2367
  const TEXT_NODE_NAME = "#text";
2362
2368
  function isTextNode(node) {
2363
- return Boolean(node && node.nodeType === NodeType.TEXT_NODE);
2369
+ return node?.nodeType === NodeType.TEXT_NODE;
2364
2370
  }
2365
2371
  class TextNode extends DOMNode {
2366
2372
  text;
@@ -2404,7 +2410,7 @@ var NodeClosed = /* @__PURE__ */ ((NodeClosed2) => {
2404
2410
  return NodeClosed2;
2405
2411
  })(NodeClosed || {});
2406
2412
  function isElementNode(node) {
2407
- return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
2413
+ return node?.nodeType === NodeType.ELEMENT_NODE;
2408
2414
  }
2409
2415
  function isInvalidTagName(tagName) {
2410
2416
  return tagName === "" || tagName === "*";
@@ -3700,6 +3706,7 @@ function interpolate(text, data) {
3700
3706
 
3701
3707
  const ajv$1 = new Ajv({ strict: true, strictTuples: true, strictTypes: true });
3702
3708
  ajv$1.addMetaSchema(ajvSchemaDraft);
3709
+ ajv$1.addKeyword(ajvRegexpKeyword);
3703
3710
  function getSchemaValidator(ruleId, properties) {
3704
3711
  const $id = `rule/${ruleId}`;
3705
3712
  const cached = ajv$1.getSchema($id);
@@ -3956,6 +3963,7 @@ class Rule {
3956
3963
  *
3957
3964
  * @internal
3958
3965
  */
3966
+ /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
3959
3967
  static validateOptions(cls, ruleId, jsonPath, options, filename, config) {
3960
3968
  if (!cls) {
3961
3969
  return;
@@ -3991,7 +3999,7 @@ class Rule {
3991
3999
  }
3992
4000
  }
3993
4001
 
3994
- const defaults$y = {
4002
+ const defaults$z = {
3995
4003
  allowExternal: true,
3996
4004
  allowRelative: true,
3997
4005
  allowAbsolute: true,
@@ -4035,7 +4043,7 @@ class AllowedLinks extends Rule {
4035
4043
  allowRelative;
4036
4044
  allowAbsolute;
4037
4045
  constructor(options) {
4038
- super({ ...defaults$y, ...options });
4046
+ super({ ...defaults$z, ...options });
4039
4047
  this.allowExternal = parseAllow(this.options.allowExternal);
4040
4048
  this.allowRelative = parseAllow(this.options.allowRelative);
4041
4049
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4203,7 +4211,7 @@ class AllowedLinks extends Rule {
4203
4211
  }
4204
4212
  }
4205
4213
 
4206
- const defaults$x = {
4214
+ const defaults$y = {
4207
4215
  accessible: true
4208
4216
  };
4209
4217
  function findByTarget(target, siblings) {
@@ -4233,7 +4241,7 @@ function getDescription$1(context) {
4233
4241
  }
4234
4242
  class AreaAlt extends Rule {
4235
4243
  constructor(options) {
4236
- super({ ...defaults$x, ...options });
4244
+ super({ ...defaults$y, ...options });
4237
4245
  }
4238
4246
  static schema() {
4239
4247
  return {
@@ -4312,8 +4320,12 @@ class AriaHiddenBody extends Rule {
4312
4320
  }
4313
4321
  }
4314
4322
 
4315
- const defaults$w = {
4316
- allowAnyNamable: false
4323
+ const defaults$x = {
4324
+ allowAnyNamable: false,
4325
+ elements: {
4326
+ include: null,
4327
+ exclude: null
4328
+ }
4317
4329
  };
4318
4330
  const allowlist = /* @__PURE__ */ new Set([
4319
4331
  "main",
@@ -4356,7 +4368,26 @@ function isValidUsage(target, meta) {
4356
4368
  }
4357
4369
  class AriaLabelMisuse extends Rule {
4358
4370
  constructor(options) {
4359
- super({ ...defaults$w, ...options });
4371
+ super({ ...defaults$x, ...options });
4372
+ }
4373
+ static schema() {
4374
+ return {
4375
+ allowAnyNamable: {
4376
+ type: "boolean"
4377
+ },
4378
+ elements: {
4379
+ type: "object",
4380
+ properties: {
4381
+ include: {
4382
+ anyOf: [{ type: "array", items: { type: "string" } }, { type: "null" }]
4383
+ },
4384
+ exclude: {
4385
+ anyOf: [{ type: "array", items: { type: "string" } }, { type: "null" }]
4386
+ }
4387
+ },
4388
+ additionalProperties: false
4389
+ }
4390
+ };
4360
4391
  }
4361
4392
  documentation(context) {
4362
4393
  const valid = [
@@ -4414,6 +4445,9 @@ class AriaLabelMisuse extends Rule {
4414
4445
  if (!meta) {
4415
4446
  return;
4416
4447
  }
4448
+ if (this.shouldIgnoreElement(target)) {
4449
+ return;
4450
+ }
4417
4451
  if (isValidUsage(target, meta)) {
4418
4452
  return;
4419
4453
  }
@@ -4438,6 +4472,9 @@ class AriaLabelMisuse extends Rule {
4438
4472
  });
4439
4473
  }
4440
4474
  }
4475
+ shouldIgnoreElement(target) {
4476
+ return isKeywordIgnored(this.options.elements, target.tagName, keywordPatternMatcher);
4477
+ }
4441
4478
  }
4442
4479
 
4443
4480
  class ConfigError extends UserError {
@@ -4500,14 +4537,14 @@ class CaseStyle {
4500
4537
  }
4501
4538
  }
4502
4539
 
4503
- const defaults$v = {
4540
+ const defaults$w = {
4504
4541
  style: "lowercase",
4505
4542
  ignoreForeign: true
4506
4543
  };
4507
4544
  class AttrCase extends Rule {
4508
4545
  style;
4509
4546
  constructor(options) {
4510
- super({ ...defaults$v, ...options });
4547
+ super({ ...defaults$w, ...options });
4511
4548
  this.style = new CaseStyle(this.options.style, "attr-case");
4512
4549
  }
4513
4550
  static schema() {
@@ -4614,7 +4651,7 @@ const MATCH_TEXTAREA_DATA = /^[^]*?(?=<\/textarea)/;
4614
4651
  const MATCH_TEXTAREA_END = /^<(\/)(textarea)/;
4615
4652
  const MATCH_TITLE_DATA = /^[^]*?(?=<\/title)/;
4616
4653
  const MATCH_TITLE_END = /^<(\/)(title)/;
4617
- const MATCH_DIRECTIVE = /^(<!--\s*\[html-validate-)([a-z0-9-]+)(\s*)(.*?)(]?\s*-->)/;
4654
+ const MATCH_DIRECTIVE = /^(<!--\s*\[?)(html-validate-)([a-z0-9-]+)(\s*)(.*?)(]?\s*-->)/;
4618
4655
  const MATCH_COMMENT = /^<!--([^]*?)-->/;
4619
4656
  const MATCH_CONDITIONAL = /^<!\[([^\]]*?)\]>/;
4620
4657
  class InvalidTokenError extends Error {
@@ -4912,7 +4949,7 @@ class AttrDelimiter extends Rule {
4912
4949
  }
4913
4950
 
4914
4951
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4915
- const defaults$u = {
4952
+ const defaults$v = {
4916
4953
  pattern: DEFAULT_PATTERN,
4917
4954
  ignoreForeign: true
4918
4955
  };
@@ -4945,7 +4982,7 @@ function generateDescription(name, pattern) {
4945
4982
  class AttrPattern extends Rule {
4946
4983
  pattern;
4947
4984
  constructor(options) {
4948
- super({ ...defaults$u, ...options });
4985
+ super({ ...defaults$v, ...options });
4949
4986
  this.pattern = generateRegexp(this.options.pattern);
4950
4987
  }
4951
4988
  static schema() {
@@ -4992,7 +5029,7 @@ class AttrPattern extends Rule {
4992
5029
  }
4993
5030
  }
4994
5031
 
4995
- const defaults$t = {
5032
+ const defaults$u = {
4996
5033
  style: "auto",
4997
5034
  unquoted: false
4998
5035
  };
@@ -5058,7 +5095,7 @@ class AttrQuotes extends Rule {
5058
5095
  };
5059
5096
  }
5060
5097
  constructor(options) {
5061
- super({ ...defaults$t, ...options });
5098
+ super({ ...defaults$u, ...options });
5062
5099
  this.style = parseStyle$3(this.options.style);
5063
5100
  }
5064
5101
  setup() {
@@ -5180,7 +5217,9 @@ class AttributeAllowedValues extends Rule {
5180
5217
  const doc = event.document;
5181
5218
  walk.depthFirst(doc, (node) => {
5182
5219
  const meta = node.meta;
5183
- if (!meta?.attributes) return;
5220
+ if (!meta?.attributes) {
5221
+ return;
5222
+ }
5184
5223
  for (const attr of node.attributes) {
5185
5224
  if (Validator.validateAttribute(attr, meta.attributes)) {
5186
5225
  continue;
@@ -5212,13 +5251,13 @@ class AttributeAllowedValues extends Rule {
5212
5251
  }
5213
5252
  }
5214
5253
 
5215
- const defaults$s = {
5254
+ const defaults$t = {
5216
5255
  style: "omit"
5217
5256
  };
5218
5257
  class AttributeBooleanStyle extends Rule {
5219
5258
  hasInvalidStyle;
5220
5259
  constructor(options) {
5221
- super({ ...defaults$s, ...options });
5260
+ super({ ...defaults$t, ...options });
5222
5261
  this.hasInvalidStyle = parseStyle$2(this.options.style);
5223
5262
  }
5224
5263
  static schema() {
@@ -5240,9 +5279,13 @@ class AttributeBooleanStyle extends Rule {
5240
5279
  const doc = event.document;
5241
5280
  walk.depthFirst(doc, (node) => {
5242
5281
  const meta = node.meta;
5243
- if (!meta?.attributes) return;
5282
+ if (!meta?.attributes) {
5283
+ return;
5284
+ }
5244
5285
  for (const attr of node.attributes) {
5245
- if (!this.isBoolean(attr, meta.attributes)) continue;
5286
+ if (!this.isBoolean(attr, meta.attributes)) {
5287
+ continue;
5288
+ }
5246
5289
  if (attr.originalAttribute) {
5247
5290
  continue;
5248
5291
  }
@@ -5284,13 +5327,13 @@ function reportMessage$1(attr, style) {
5284
5327
  return "";
5285
5328
  }
5286
5329
 
5287
- const defaults$r = {
5330
+ const defaults$s = {
5288
5331
  style: "omit"
5289
5332
  };
5290
5333
  class AttributeEmptyStyle extends Rule {
5291
5334
  hasInvalidStyle;
5292
5335
  constructor(options) {
5293
- super({ ...defaults$r, ...options });
5336
+ super({ ...defaults$s, ...options });
5294
5337
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5295
5338
  }
5296
5339
  static schema() {
@@ -5312,7 +5355,9 @@ class AttributeEmptyStyle extends Rule {
5312
5355
  const doc = event.document;
5313
5356
  walk.depthFirst(doc, (node) => {
5314
5357
  const meta = node.meta;
5315
- if (!meta?.attributes) return;
5358
+ if (!meta?.attributes) {
5359
+ return;
5360
+ }
5316
5361
  for (const attr of node.attributes) {
5317
5362
  if (!allowsEmpty(attr, meta.attributes)) {
5318
5363
  continue;
@@ -5405,7 +5450,22 @@ class AttributeMisuse extends Rule {
5405
5450
  }
5406
5451
  }
5407
5452
 
5453
+ const patternNamesValues = [
5454
+ "kebabcase",
5455
+ "camelcase",
5456
+ "underscore",
5457
+ "snakecase",
5458
+ "bem",
5459
+ "tailwind"
5460
+ ];
5461
+ const patternNames = new Set(patternNamesValues);
5462
+ function isNamedPattern(value) {
5463
+ return typeof value === "string" && patternNames.has(value);
5464
+ }
5408
5465
  function parsePattern(pattern) {
5466
+ if (pattern instanceof RegExp) {
5467
+ return { regexp: pattern, description: pattern.toString() };
5468
+ }
5409
5469
  switch (pattern) {
5410
5470
  case "kebabcase":
5411
5471
  return { regexp: /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/, description: pattern };
@@ -5423,7 +5483,21 @@ function parsePattern(pattern) {
5423
5483
  description: pattern
5424
5484
  };
5425
5485
  }
5486
+ case "tailwind": {
5487
+ return {
5488
+ regexp: /^!?(?:[-a-z[]|\d+xl:)[\w\-:./\\[\]()#'&>,!=%]*$/,
5489
+ description: "tailwind"
5490
+ };
5491
+ }
5426
5492
  default: {
5493
+ if (pattern.startsWith("/") && pattern.endsWith("/")) {
5494
+ const regexpSource = pattern.slice(1, -1);
5495
+ const regexp2 = new RegExp(regexpSource);
5496
+ return { regexp: regexp2, description: regexp2.toString() };
5497
+ }
5498
+ console.warn(
5499
+ `Custom pattern "${pattern}" should be wrapped in forward slashes, e.g., "/${pattern}/". Support for unwrapped patterns is deprecated and will be removed in a future version.`
5500
+ );
5427
5501
  const regexp = new RegExp(pattern);
5428
5502
  return { regexp, description: regexp.toString() };
5429
5503
  }
@@ -5431,27 +5505,49 @@ function parsePattern(pattern) {
5431
5505
  }
5432
5506
 
5433
5507
  function toArray$2(value) {
5434
- return Array.isArray(value) ? value : [value];
5508
+ if (Array.isArray(value)) {
5509
+ return value;
5510
+ } else {
5511
+ return [value];
5512
+ }
5513
+ }
5514
+ function validateAllowedPatterns(patterns, allowedPatterns, ruleId) {
5515
+ const extraneous = patterns.filter(isNamedPattern).filter((p) => !allowedPatterns.has(p));
5516
+ if (extraneous.length > 0) {
5517
+ const quote = (it) => `"${it}"`;
5518
+ const disallowed = naturalJoin(extraneous.map(quote), "and");
5519
+ const allowed = naturalJoin(Array.from(allowedPatterns, quote), "and");
5520
+ throw new Error(
5521
+ `Pattern ${disallowed} cannot be used with "${ruleId}". Allowed patterns: ${allowed}`
5522
+ );
5523
+ }
5435
5524
  }
5436
5525
  class BasePatternRule extends Rule {
5437
5526
  /** Attribute being tested */
5438
5527
  attr;
5439
5528
  /** Parsed configured patterns */
5440
5529
  patterns;
5441
- /**
5442
- * @param attr - Attribute holding the value.
5443
- * @param options - Rule options with defaults expanded.
5444
- */
5445
- constructor(attr, options) {
5530
+ constructor({
5531
+ ruleId,
5532
+ attr,
5533
+ options,
5534
+ allowedPatterns
5535
+ }) {
5446
5536
  super(options);
5447
5537
  const { pattern } = this.options;
5448
5538
  this.attr = attr;
5449
- this.patterns = toArray$2(pattern).map((it) => parsePattern(it));
5539
+ const patterns = toArray$2(pattern);
5540
+ validateAllowedPatterns(patterns, allowedPatterns, ruleId);
5541
+ this.patterns = patterns.map((it) => parsePattern(it));
5450
5542
  }
5451
5543
  static schema() {
5452
5544
  return {
5453
5545
  pattern: {
5454
- oneOf: [{ type: "array", items: { type: "string" }, minItems: 1 }, { type: "string" }]
5546
+ anyOf: [
5547
+ { type: "array", items: { anyOf: [{ type: "string" }, { regexp: true }] }, minItems: 1 },
5548
+ { type: "string" },
5549
+ { regexp: true }
5550
+ ]
5455
5551
  }
5456
5552
  };
5457
5553
  }
@@ -5485,12 +5581,18 @@ class BasePatternRule extends Rule {
5485
5581
  }
5486
5582
  }
5487
5583
 
5488
- const defaults$q = {
5584
+ const defaults$r = {
5489
5585
  pattern: "kebabcase"
5490
5586
  };
5491
5587
  class ClassPattern extends BasePatternRule {
5492
5588
  constructor(options) {
5493
- super("class", { ...defaults$q, ...options });
5589
+ super({
5590
+ ruleId: "class-pattern",
5591
+ attr: "class",
5592
+ options: { ...defaults$r, ...options },
5593
+ allowedPatterns: patternNames
5594
+ // allow all patterns
5595
+ });
5494
5596
  }
5495
5597
  static schema() {
5496
5598
  return BasePatternRule.schema();
@@ -5637,13 +5739,13 @@ class CloseOrder extends Rule {
5637
5739
  }
5638
5740
  }
5639
5741
 
5640
- const defaults$p = {
5742
+ const defaults$q = {
5641
5743
  include: null,
5642
5744
  exclude: null
5643
5745
  };
5644
5746
  class Deprecated extends Rule {
5645
5747
  constructor(options) {
5646
- super({ ...defaults$p, ...options });
5748
+ super({ ...defaults$q, ...options });
5647
5749
  }
5648
5750
  static schema() {
5649
5751
  return {
@@ -5797,12 +5899,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5797
5899
  }
5798
5900
  };
5799
5901
 
5800
- const defaults$o = {
5902
+ const defaults$p = {
5801
5903
  style: "uppercase"
5802
5904
  };
5803
5905
  class DoctypeStyle extends Rule {
5804
5906
  constructor(options) {
5805
- super({ ...defaults$o, ...options });
5907
+ super({ ...defaults$p, ...options });
5806
5908
  }
5807
5909
  static schema() {
5808
5910
  return {
@@ -5830,13 +5932,13 @@ class DoctypeStyle extends Rule {
5830
5932
  }
5831
5933
  }
5832
5934
 
5833
- const defaults$n = {
5935
+ const defaults$o = {
5834
5936
  style: "lowercase"
5835
5937
  };
5836
5938
  class ElementCase extends Rule {
5837
5939
  style;
5838
5940
  constructor(options) {
5839
- super({ ...defaults$n, ...options });
5941
+ super({ ...defaults$o, ...options });
5840
5942
  this.style = new CaseStyle(this.options.style, "element-case");
5841
5943
  }
5842
5944
  static schema() {
@@ -5896,7 +5998,7 @@ class ElementCase extends Rule {
5896
5998
  }
5897
5999
  }
5898
6000
 
5899
- const defaults$m = {
6001
+ const defaults$n = {
5900
6002
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5901
6003
  whitelist: [],
5902
6004
  blacklist: []
@@ -5904,7 +6006,7 @@ const defaults$m = {
5904
6006
  class ElementName extends Rule {
5905
6007
  pattern;
5906
6008
  constructor(options) {
5907
- super({ ...defaults$m, ...options });
6009
+ super({ ...defaults$n, ...options });
5908
6010
  this.pattern = new RegExp(this.options.pattern);
5909
6011
  }
5910
6012
  static schema() {
@@ -5941,7 +6043,7 @@ class ElementName extends Rule {
5941
6043
  ...context.blacklist.map((cur) => `- ${cur}`)
5942
6044
  ];
5943
6045
  }
5944
- if (context.pattern !== defaults$m.pattern) {
6046
+ if (context.pattern !== defaults$n.pattern) {
5945
6047
  return [
5946
6048
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5947
6049
  "",
@@ -6440,7 +6542,9 @@ class EmptyTitle extends Rule {
6440
6542
  setup() {
6441
6543
  this.on("tag:end", (event) => {
6442
6544
  const node = event.previous;
6443
- if (node.tagName !== "title") return;
6545
+ if (node.tagName !== "title") {
6546
+ return;
6547
+ }
6444
6548
  switch (classifyNodeText(node)) {
6445
6549
  case TextClassification.DYNAMIC_TEXT:
6446
6550
  case TextClassification.STATIC_TEXT:
@@ -6456,7 +6560,7 @@ class EmptyTitle extends Rule {
6456
6560
  }
6457
6561
  }
6458
6562
 
6459
- const defaults$l = {
6563
+ const defaults$m = {
6460
6564
  allowArrayBrackets: true,
6461
6565
  allowCheckboxDefault: true,
6462
6566
  shared: ["radio", "button", "reset", "submit"]
@@ -6516,7 +6620,7 @@ function getDocumentation(context) {
6516
6620
  }
6517
6621
  class FormDupName extends Rule {
6518
6622
  constructor(options) {
6519
- super({ ...defaults$l, ...options });
6623
+ super({ ...defaults$m, ...options });
6520
6624
  }
6521
6625
  static schema() {
6522
6626
  return {
@@ -6675,7 +6779,7 @@ class FormDupName extends Rule {
6675
6779
  }
6676
6780
  }
6677
6781
 
6678
- const defaults$k = {
6782
+ const defaults$l = {
6679
6783
  allowMultipleH1: false,
6680
6784
  minInitialRank: "h1",
6681
6785
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
@@ -6707,7 +6811,7 @@ class HeadingLevel extends Rule {
6707
6811
  sectionRoots;
6708
6812
  stack = [];
6709
6813
  constructor(options) {
6710
- super({ ...defaults$k, ...options });
6814
+ super({ ...defaults$l, ...options });
6711
6815
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
6712
6816
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Compound(it));
6713
6817
  this.stack.push({
@@ -6761,7 +6865,9 @@ class HeadingLevel extends Rule {
6761
6865
  }
6762
6866
  onTagStart(event) {
6763
6867
  const level = extractLevel(event.target);
6764
- if (!level) return;
6868
+ if (!level) {
6869
+ return;
6870
+ }
6765
6871
  const root = this.getCurrentRoot();
6766
6872
  if (!this.options.allowMultipleH1 && level === 1) {
6767
6873
  if (root.h1Count >= 1) {
@@ -6944,12 +7050,25 @@ class HiddenFocusable extends Rule {
6944
7050
  }
6945
7051
  }
6946
7052
 
6947
- const defaults$j = {
7053
+ const defaults$k = {
6948
7054
  pattern: "kebabcase"
6949
7055
  };
7056
+ function exclude$1(set, ...values) {
7057
+ const result = new Set(set);
7058
+ for (const value of values) {
7059
+ result.delete(value);
7060
+ }
7061
+ return result;
7062
+ }
6950
7063
  class IdPattern extends BasePatternRule {
6951
7064
  constructor(options) {
6952
- super("id", { ...defaults$j, ...options });
7065
+ const allowedPatterns = exclude$1(patternNames, "tailwind");
7066
+ super({
7067
+ ruleId: "id-pattern",
7068
+ attr: "id",
7069
+ options: { ...defaults$k, ...options },
7070
+ allowedPatterns
7071
+ });
6953
7072
  }
6954
7073
  static schema() {
6955
7074
  return BasePatternRule.schema();
@@ -7253,7 +7372,9 @@ class InputMissingLabel extends Rule {
7253
7372
  }
7254
7373
  }
7255
7374
  function findLabelById(root, id) {
7256
- if (!id) return [];
7375
+ if (!id) {
7376
+ return [];
7377
+ }
7257
7378
  return root.querySelectorAll(`label[for="${id}"]`);
7258
7379
  }
7259
7380
  function findLabelByParent(el) {
@@ -7267,13 +7388,13 @@ function findLabelByParent(el) {
7267
7388
  return [];
7268
7389
  }
7269
7390
 
7270
- const defaults$i = {
7391
+ const defaults$j = {
7271
7392
  maxlength: 70
7272
7393
  };
7273
7394
  class LongTitle extends Rule {
7274
7395
  maxlength;
7275
7396
  constructor(options) {
7276
- super({ ...defaults$i, ...options });
7397
+ super({ ...defaults$j, ...options });
7277
7398
  this.maxlength = this.options.maxlength;
7278
7399
  }
7279
7400
  static schema() {
@@ -7292,7 +7413,9 @@ class LongTitle extends Rule {
7292
7413
  setup() {
7293
7414
  this.on("tag:end", (event) => {
7294
7415
  const node = event.previous;
7295
- if (node.tagName !== "title") return;
7416
+ if (node.tagName !== "title") {
7417
+ return;
7418
+ }
7296
7419
  const text = node.textContent;
7297
7420
  if (text.length > this.maxlength) {
7298
7421
  this.report(node, `title text cannot be longer than ${String(this.maxlength)} characters`);
@@ -7375,12 +7498,12 @@ class MapIdName extends Rule {
7375
7498
  }
7376
7499
  }
7377
7500
 
7378
- const defaults$h = {
7501
+ const defaults$i = {
7379
7502
  allowLongDelay: false
7380
7503
  };
7381
7504
  class MetaRefresh extends Rule {
7382
7505
  constructor(options) {
7383
- super({ ...defaults$h, ...options });
7506
+ super({ ...defaults$i, ...options });
7384
7507
  }
7385
7508
  documentation() {
7386
7509
  return {
@@ -7491,12 +7614,25 @@ class MultipleLabeledControls extends Rule {
7491
7614
  }
7492
7615
  }
7493
7616
 
7494
- const defaults$g = {
7617
+ const defaults$h = {
7495
7618
  pattern: "camelcase"
7496
7619
  };
7620
+ function exclude(set, ...values) {
7621
+ const result = new Set(set);
7622
+ for (const value of values) {
7623
+ result.delete(value);
7624
+ }
7625
+ return result;
7626
+ }
7497
7627
  class NamePattern extends BasePatternRule {
7498
7628
  constructor(options) {
7499
- super("name", { ...defaults$g, ...options });
7629
+ const allowedPatterns = exclude(patternNames, "tailwind");
7630
+ super({
7631
+ ruleId: "name-pattern",
7632
+ attr: "name",
7633
+ options: { ...defaults$h, ...options },
7634
+ allowedPatterns
7635
+ });
7500
7636
  }
7501
7637
  static schema() {
7502
7638
  return BasePatternRule.schema();
@@ -7585,13 +7721,13 @@ class NoAbstractRole extends Rule {
7585
7721
  }
7586
7722
  }
7587
7723
 
7588
- const defaults$f = {
7724
+ const defaults$g = {
7589
7725
  include: null,
7590
7726
  exclude: null
7591
7727
  };
7592
7728
  class NoAutoplay extends Rule {
7593
7729
  constructor(options) {
7594
- super({ ...defaults$f, ...options });
7730
+ super({ ...defaults$g, ...options });
7595
7731
  }
7596
7732
  documentation(context) {
7597
7733
  return {
@@ -7857,7 +7993,7 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
7857
7993
  return;
7858
7994
  }
7859
7995
  const parent = closed.parent;
7860
- const closedByParent = parent && parent.tagName === by.tagName;
7996
+ const closedByParent = parent?.tagName === by.tagName;
7861
7997
  const closedByDocument = closedByParent && parent.isRootElement();
7862
7998
  const sameTag = closed.tagName === by.tagName;
7863
7999
  if (closedByDocument) {
@@ -7913,14 +8049,14 @@ class NoImplicitInputType extends Rule {
7913
8049
  }
7914
8050
  }
7915
8051
 
7916
- const defaults$e = {
8052
+ const defaults$f = {
7917
8053
  include: null,
7918
8054
  exclude: null,
7919
8055
  allowedProperties: ["display"]
7920
8056
  };
7921
8057
  class NoInlineStyle extends Rule {
7922
8058
  constructor(options) {
7923
- super({ ...defaults$e, ...options });
8059
+ super({ ...defaults$f, ...options });
7924
8060
  }
7925
8061
  static schema() {
7926
8062
  return {
@@ -8106,7 +8242,7 @@ class NoMultipleMain extends Rule {
8106
8242
  }
8107
8243
  }
8108
8244
 
8109
- const defaults$d = {
8245
+ const defaults$e = {
8110
8246
  relaxed: false
8111
8247
  };
8112
8248
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -8124,7 +8260,7 @@ const replacementTable = {
8124
8260
  class NoRawCharacters extends Rule {
8125
8261
  relaxed;
8126
8262
  constructor(options) {
8127
- super({ ...defaults$d, ...options });
8263
+ super({ ...defaults$e, ...options });
8128
8264
  this.relaxed = this.options.relaxed;
8129
8265
  }
8130
8266
  static schema() {
@@ -8219,7 +8355,7 @@ class NoRedundantAriaLabel extends Rule {
8219
8355
  continue;
8220
8356
  }
8221
8357
  const label = document.querySelector(`label[for="${id}"]`);
8222
- if (!ariaLabel || !label || label.textContent.trim() !== ariaLabel.value) {
8358
+ if (!ariaLabel || label?.textContent.trim() !== ariaLabel.value) {
8223
8359
  continue;
8224
8360
  }
8225
8361
  const message = "aria-label is redundant when label containing same text exists";
@@ -8304,13 +8440,13 @@ class NoRedundantRole extends Rule {
8304
8440
  }
8305
8441
 
8306
8442
  const xmlns = /^(.+):.+$/;
8307
- const defaults$c = {
8443
+ const defaults$d = {
8308
8444
  ignoreForeign: true,
8309
8445
  ignoreXML: true
8310
8446
  };
8311
8447
  class NoSelfClosing extends Rule {
8312
8448
  constructor(options) {
8313
- super({ ...defaults$c, ...options });
8449
+ super({ ...defaults$d, ...options });
8314
8450
  }
8315
8451
  static schema() {
8316
8452
  return {
@@ -8360,7 +8496,20 @@ function isRelevant(node, options) {
8360
8496
  return true;
8361
8497
  }
8362
8498
 
8499
+ const defaults$c = {
8500
+ allowTemplate: true
8501
+ };
8363
8502
  class NoStyleTag extends Rule {
8503
+ constructor(options) {
8504
+ super({ ...defaults$c, ...options });
8505
+ }
8506
+ static schema() {
8507
+ return {
8508
+ allowTemplate: {
8509
+ type: "boolean"
8510
+ }
8511
+ };
8512
+ }
8364
8513
  documentation() {
8365
8514
  return {
8366
8515
  description: "Prefer to use external stylesheets with the `<link>` tag instead of inlining the styling.",
@@ -8368,9 +8517,13 @@ class NoStyleTag extends Rule {
8368
8517
  };
8369
8518
  }
8370
8519
  setup() {
8520
+ const { allowTemplate } = this.options;
8371
8521
  this.on("tag:start", (event) => {
8372
8522
  const node = event.target;
8373
8523
  if (node.tagName === "style") {
8524
+ if (allowTemplate && node.parent?.is("template")) {
8525
+ return;
8526
+ }
8374
8527
  this.report(node, "Use external stylesheet with <link> instead of <style> tag");
8375
8528
  }
8376
8529
  });
@@ -9277,7 +9430,7 @@ function getTextFromReference(document, id) {
9277
9430
  if (!id || id instanceof DynamicValue) {
9278
9431
  return id;
9279
9432
  }
9280
- const selector = `#${id}`;
9433
+ const selector = generateIdSelector(id);
9281
9434
  const ref = document.querySelector(selector);
9282
9435
  if (ref) {
9283
9436
  return ref.textContent;
@@ -10038,6 +10191,46 @@ class ValidAutocomplete extends Rule {
10038
10191
  }
10039
10192
  }
10040
10193
 
10194
+ function isLabelable(target) {
10195
+ const { meta } = target;
10196
+ if (!meta) {
10197
+ return true;
10198
+ }
10199
+ return Boolean(meta.labelable);
10200
+ }
10201
+ class ValidFor extends Rule {
10202
+ documentation() {
10203
+ return {
10204
+ description: `The \`<label>\` \`for\` attribute must reference a labelable form control.`,
10205
+ url: "https://html-validate.org/rules/valid-for.html"
10206
+ };
10207
+ }
10208
+ setup() {
10209
+ this.on("dom:ready", (event) => {
10210
+ const { document } = event;
10211
+ for (const node of document.querySelectorAll("label[for]")) {
10212
+ const attr = node.getAttribute("for");
10213
+ if (!isStaticAttribute(attr) || !attr.value) {
10214
+ continue;
10215
+ }
10216
+ const selector = generateIdSelector(attr.value);
10217
+ const target = document.querySelector(selector);
10218
+ if (!target) {
10219
+ continue;
10220
+ }
10221
+ if (isLabelable(target)) {
10222
+ continue;
10223
+ }
10224
+ this.report({
10225
+ node,
10226
+ message: '<label> "for" attribute must reference a labelable form control',
10227
+ location: attr.valueLocation
10228
+ });
10229
+ }
10230
+ });
10231
+ }
10232
+ }
10233
+
10041
10234
  const defaults$4 = {
10042
10235
  relaxed: false
10043
10236
  };
@@ -10336,7 +10529,9 @@ class H36 extends Rule {
10336
10529
  setup() {
10337
10530
  this.on("tag:end", (event) => {
10338
10531
  const node = event.previous;
10339
- if (node.tagName !== "input") return;
10532
+ if (node.tagName !== "input") {
10533
+ return;
10534
+ }
10340
10535
  if (node.getAttributeValue("type") !== "image") {
10341
10536
  return;
10342
10537
  }
@@ -10662,6 +10857,7 @@ const bundledRules = {
10662
10857
  "unique-landmark": UniqueLandmark,
10663
10858
  "unrecognized-char-ref": UnknownCharReference,
10664
10859
  "valid-autocomplete": ValidAutocomplete,
10860
+ "valid-for": ValidFor,
10665
10861
  "valid-id": ValidID,
10666
10862
  "void-content": VoidContent,
10667
10863
  "void-style": VoidStyle,
@@ -11015,6 +11211,7 @@ const config$1 = {
11015
11211
  "unique-landmark": "error",
11016
11212
  "unrecognized-char-ref": "error",
11017
11213
  "valid-autocomplete": "error",
11214
+ "valid-for": "error",
11018
11215
  "valid-id": ["error", { relaxed: false }],
11019
11216
  void: "off",
11020
11217
  "void-content": "error",
@@ -11062,6 +11259,7 @@ const config = {
11062
11259
  "script-element": "error",
11063
11260
  "unrecognized-char-ref": "error",
11064
11261
  "valid-autocomplete": "error",
11262
+ "valid-for": "error",
11065
11263
  "valid-id": ["error", { relaxed: true }],
11066
11264
  "void-content": "error"
11067
11265
  }
@@ -11668,7 +11866,9 @@ class Config {
11668
11866
  }
11669
11867
  for (const plugin of plugins) {
11670
11868
  for (const [name, config] of Object.entries(plugin.configs ?? {})) {
11671
- if (!config) continue;
11869
+ if (!config) {
11870
+ continue;
11871
+ }
11672
11872
  Config.validate(config, name);
11673
11873
  configs.set(`${plugin.name}:${name}`, config);
11674
11874
  if (plugin.name !== plugin.originalName) {
@@ -11969,7 +12169,7 @@ class EventHandler {
11969
12169
  }
11970
12170
 
11971
12171
  const name = "html-validate";
11972
- const version = "10.5.0";
12172
+ const version = "10.7.0";
11973
12173
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11974
12174
 
11975
12175
  function freeze(src) {
@@ -12020,6 +12220,7 @@ class Reporter {
12020
12220
  warningCount: sumWarnings(results)
12021
12221
  };
12022
12222
  }
12223
+ /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
12023
12224
  add(rule, message, severity, node, location, context) {
12024
12225
  if (!(location.filename in this.result)) {
12025
12226
  this.result[location.filename] = [];
@@ -12139,7 +12340,7 @@ class ParserError extends Error {
12139
12340
  }
12140
12341
 
12141
12342
  function isAttrValueToken(token) {
12142
- return Boolean(token && token.type === TokenType.ATTR_VALUE);
12343
+ return token?.type === TokenType.ATTR_VALUE;
12143
12344
  }
12144
12345
  function svgShouldRetainTag(foreignTagName, tagName) {
12145
12346
  return foreignTagName === "svg" && ["title", "desc"].includes(tagName);
@@ -12485,7 +12686,7 @@ class Parser {
12485
12686
  * ^^^ ^^^ ^^^ (null) (null)
12486
12687
  */
12487
12688
  getAttributeValueLocation(token) {
12488
- if (!token || token.type !== TokenType.ATTR_VALUE || token.data[2] === "") {
12689
+ if (token?.type !== TokenType.ATTR_VALUE || token.data[2] === "") {
12489
12690
  return null;
12490
12691
  }
12491
12692
  const quote = token.data[3];
@@ -12501,7 +12702,7 @@ class Parser {
12501
12702
  */
12502
12703
  getAttributeLocation(key, value) {
12503
12704
  const begin = key.location;
12504
- const end = value && value.type === TokenType.ATTR_VALUE ? value.location : void 0;
12705
+ const end = value?.type === TokenType.ATTR_VALUE ? value.location : void 0;
12505
12706
  return {
12506
12707
  filename: begin.filename,
12507
12708
  line: begin.line,
@@ -12514,27 +12715,34 @@ class Parser {
12514
12715
  * @internal
12515
12716
  */
12516
12717
  consumeDirective(token) {
12517
- const [text, preamble, action, separator1, directive, postamble] = token.data;
12518
- if (!postamble.startsWith("]")) {
12519
- throw new ParserError(token.location, `Missing end bracket "]" on directive "${text}"`);
12718
+ const [text, preamble, prefix, action, separator1, directive, postamble] = token.data;
12719
+ const hasStartBracket = preamble.includes("[");
12720
+ const hasEndBracket = postamble.startsWith("]");
12721
+ if (hasStartBracket && !hasEndBracket) {
12722
+ this.trigger("parse:error", {
12723
+ location: sliceLocation(token.location, preamble.length - 1, -postamble.length),
12724
+ message: `Missing end bracket "]" on directive "${text}"`
12725
+ });
12726
+ return;
12520
12727
  }
12521
12728
  const match = /^(.*?)(?:(\s*(?:--|:)\s*)(.*))?$/.exec(directive);
12522
12729
  if (!match) {
12523
12730
  throw new Error(`Failed to parse directive "${text}"`);
12524
12731
  }
12525
12732
  if (!isValidDirective(action)) {
12526
- throw new ParserError(token.location, `Unknown directive "${action}"`);
12733
+ const begin = preamble.length;
12734
+ const end = preamble.length + prefix.length + action.length;
12735
+ this.trigger("parse:error", {
12736
+ location: sliceLocation(token.location, begin, -text.length + end),
12737
+ message: `Unknown directive "${action}"`
12738
+ });
12739
+ return;
12527
12740
  }
12528
12741
  const [, data, separator2, comment] = match;
12529
- const prefix = "html-validate-";
12530
- const actionOffset = preamble.length;
12742
+ const actionOffset = preamble.length + prefix.length;
12531
12743
  const optionsOffset = actionOffset + action.length + separator1.length;
12532
12744
  const commentOffset = optionsOffset + data.length + (separator2 || "").length;
12533
- const location = sliceLocation(
12534
- token.location,
12535
- preamble.length - prefix.length - 1,
12536
- -postamble.length + 1
12537
- );
12745
+ const location = sliceLocation(token.location, preamble.length - 1, -postamble.length + 1);
12538
12746
  const actionLocation = sliceLocation(
12539
12747
  token.location,
12540
12748
  actionOffset,
@@ -12616,7 +12824,9 @@ class Parser {
12616
12824
  while (!it.done) {
12617
12825
  const token = it.value;
12618
12826
  yield token;
12619
- if (token.type === search) return;
12827
+ if (token.type === search) {
12828
+ return;
12829
+ }
12620
12830
  it = this.next(tokenStream);
12621
12831
  }
12622
12832
  throw new ParserError(
@@ -12779,6 +12989,9 @@ class Engine {
12779
12989
  parser.on("directive", (_2, event) => {
12780
12990
  this.processDirective(event, parser, directiveContext);
12781
12991
  });
12992
+ parser.on("parse:error", (_2, event) => {
12993
+ this.reportError("parser-error", event.message, event.location);
12994
+ });
12782
12995
  try {
12783
12996
  parser.parseHtml(source);
12784
12997
  } catch (e) {
@@ -12976,7 +13189,9 @@ class Engine {
12976
13189
  const availableRules = {};
12977
13190
  for (const plugin of config.getPlugins()) {
12978
13191
  for (const [name, rule] of Object.entries(plugin.rules ?? {})) {
12979
- if (!rule) continue;
13192
+ if (!rule) {
13193
+ continue;
13194
+ }
12980
13195
  availableRules[name] = rule;
12981
13196
  }
12982
13197
  }
@@ -13009,6 +13224,7 @@ class Engine {
13009
13224
  /**
13010
13225
  * Load and setup a rule using current config.
13011
13226
  */
13227
+ /* eslint-disable-next-line @typescript-eslint/max-params -- technical debt */
13012
13228
  loadRule(ruleId, config, severity, options, parser, report) {
13013
13229
  const meta = config.getMetaTable();
13014
13230
  const rule = this.instantiateRule(ruleId, options);
@@ -14373,5 +14589,5 @@ const engines = {
14373
14589
 
14374
14590
  var workerPath = "./jest-worker.js";
14375
14591
 
14376
- export { compatibilityCheckImpl as $, Attribute as A, Reporter as B, Config as C, DOMNode as D, definePlugin as E, ruleExists as F, walk as G, HtmlElement as H, EventHandler as I, engines as J, normalizeSource as K, transformSource as L, MetaCopyableProperty as M, NodeClosed as N, Engine as O, Parser as P, transformSourceSync as Q, ResolvedConfig as R, Severity as S, TextNode as T, UserError as U, Validator as V, WrappedError as W, transformFilename as X, transformFilenameSync as Y, configurationSchema as Z, isThenable as _, ConfigError as a, workerPath as a0, codeframe as a1, name as a2, bugs as a3, ConfigLoader as b, defineConfig as c, deepmerge as d, ensureError as e, StaticConfigLoader as f, getFormatter as g, DOMTokenList as h, ignore as i, DOMTree as j, DynamicValue as k, NodeType as l, NestedError as m, SchemaValidationError as n, isUserError as o, presets as p, MetaTable as q, TextContent$1 as r, staticResolver as s, Rule as t, TextClassification as u, version as v, ariaNaming as w, classifyNodeText as x, keywordPatternMatcher as y, sliceLocation as z };
14592
+ export { engines as $, Attribute as A, isUserError as B, ConfigLoader as C, DOMNode as D, Engine as E, MetaTable as F, TextContent$1 as G, HtmlElement as H, Rule as I, TextClassification as J, ariaNaming as K, classifyNodeText as L, MetaCopyableProperty as M, NodeClosed as N, keywordPatternMatcher as O, Parser as P, sliceLocation as Q, Reporter as R, StaticConfigLoader as S, TextNode as T, UserError as U, Validator as V, WrappedError as W, definePlugin as X, ruleExists as Y, walk as Z, EventHandler as _, transformSourceSync as a, workerPath as a0, codeframe as a1, name as a2, bugs as a3, transformFilename as b, transformFilenameSync as c, configurationSchema as d, ConfigError as e, Config as f, compatibilityCheckImpl as g, getFormatter as h, isThenable as i, ensureError as j, deepmerge as k, ignore as l, ResolvedConfig as m, normalizeSource as n, Severity as o, presets as p, defineConfig as q, DOMTokenList as r, staticResolver as s, transformSource as t, DOMTree as u, version as v, DynamicValue as w, NodeType as x, NestedError as y, SchemaValidationError as z };
14377
14593
  //# sourceMappingURL=core.js.map