html-validate 8.9.1 → 8.11.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
@@ -584,8 +584,13 @@ const patternProperties = {
584
584
  implicitRole: {
585
585
  title: "Implicit ARIA role for this element",
586
586
  description: "Some elements have implicit ARIA roles.",
587
+ deprecated: true,
587
588
  "function": true
588
589
  },
590
+ aria: {
591
+ title: "WAI-ARIA properties for this element",
592
+ $ref: "#/definitions/Aria"
593
+ },
589
594
  scriptSupporting: {
590
595
  title: "Mark element as script-supporting",
591
596
  description: "Script-supporting elements are elements which can be inserted where othersise not permitted to assist in templating",
@@ -682,6 +687,39 @@ const patternProperties = {
682
687
  }
683
688
  };
684
689
  const definitions = {
690
+ Aria: {
691
+ type: "object",
692
+ additionalProperties: false,
693
+ properties: {
694
+ implicitRole: {
695
+ title: "Implicit ARIA role for this element",
696
+ description: "Some elements have implicit ARIA roles.",
697
+ anyOf: [
698
+ {
699
+ type: "string"
700
+ },
701
+ {
702
+ "function": true
703
+ }
704
+ ]
705
+ },
706
+ naming: {
707
+ title: "Prohibit or allow this element to be named by aria-label or aria-labelledby",
708
+ anyOf: [
709
+ {
710
+ type: "string",
711
+ "enum": [
712
+ "prohibited",
713
+ "allowed"
714
+ ]
715
+ },
716
+ {
717
+ "function": true
718
+ }
719
+ ]
720
+ }
721
+ }
722
+ },
685
723
  contentCategory: {
686
724
  anyOf: [
687
725
  {
@@ -982,6 +1020,7 @@ const MetaCopyableProperty = [
982
1020
  "formAssociated",
983
1021
  "labelable",
984
1022
  "attributes",
1023
+ "aria",
985
1024
  "permittedContent",
986
1025
  "permittedDescendants",
987
1026
  "permittedOrder",
@@ -1042,7 +1081,27 @@ function migrateAttributes(src) {
1042
1081
  });
1043
1082
  return Object.fromEntries(entries);
1044
1083
  }
1084
+ function normalizeAriaImplicitRole(value) {
1085
+ if (!value) {
1086
+ return () => null;
1087
+ }
1088
+ if (typeof value === "string") {
1089
+ return () => value;
1090
+ }
1091
+ return value;
1092
+ }
1093
+ function normalizeAriaNaming(value) {
1094
+ if (!value) {
1095
+ return () => "allowed";
1096
+ }
1097
+ if (typeof value === "string") {
1098
+ return () => value;
1099
+ }
1100
+ return value;
1101
+ }
1045
1102
  function migrateElement(src) {
1103
+ var _a, _b;
1104
+ const implicitRole = normalizeAriaImplicitRole(src.implicitRole ?? ((_a = src.aria) == null ? void 0 : _a.implicitRole));
1046
1105
  const result = {
1047
1106
  ...src,
1048
1107
  ...{
@@ -1051,7 +1110,11 @@ function migrateElement(src) {
1051
1110
  attributes: migrateAttributes(src),
1052
1111
  textContent: src.textContent,
1053
1112
  focusable: src.focusable ?? false,
1054
- implicitRole: src.implicitRole ?? (() => null)
1113
+ implicitRole,
1114
+ aria: {
1115
+ implicitRole,
1116
+ naming: normalizeAriaNaming((_b = src.aria) == null ? void 0 : _b.naming)
1117
+ }
1055
1118
  };
1056
1119
  delete result.deprecatedAttributes;
1057
1120
  delete result.requiredAttributes;
@@ -1893,8 +1956,30 @@ function factory$1(name, context) {
1893
1956
  function stripslashes(value) {
1894
1957
  return value.replace(/\\(.)/g, "$1");
1895
1958
  }
1959
+ function unescapeCodepoint(value) {
1960
+ const replacement = {
1961
+ "\\9 ": " ",
1962
+ "\\a ": "\n",
1963
+ "\\d ": "\r"
1964
+ };
1965
+ return value.replace(
1966
+ /(\\[\u0039\u0061\u0064] )/g,
1967
+ (_, codepoint) => replacement[codepoint]
1968
+ );
1969
+ }
1896
1970
  function escapeSelectorComponent(text) {
1897
- return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
1971
+ const codepoints = {
1972
+ " ": "\\9 ",
1973
+ "\n": "\\a ",
1974
+ "\r": "\\d "
1975
+ };
1976
+ return text.toString().replace(/([\t\n\r]|[^a-z0-9_-])/gi, (_, ch) => {
1977
+ if (codepoints[ch]) {
1978
+ return codepoints[ch];
1979
+ } else {
1980
+ return `\\${ch}`;
1981
+ }
1982
+ });
1898
1983
  }
1899
1984
  function generateIdSelector(id) {
1900
1985
  const escaped = escapeSelectorComponent(id);
@@ -2009,7 +2094,10 @@ class PseudoClassMatcher extends Matcher {
2009
2094
  }
2010
2095
  class Pattern {
2011
2096
  constructor(pattern) {
2012
- const match = pattern.match(/^([~+\->]?)((?:[*]|[^.#[:]+)?)(.*)$/);
2097
+ const match = pattern.match(/^([~+\->]?)((?:[*]|[^.#[:]+)?)([^]*)$/);
2098
+ if (!match) {
2099
+ throw new Error(`Failed to create selector pattern from "${pattern}"`);
2100
+ }
2013
2101
  match.shift();
2014
2102
  this.selector = pattern;
2015
2103
  this.combinator = parseCombinator(match.shift(), pattern);
@@ -2065,10 +2153,10 @@ class Selector {
2065
2153
  static parse(selector) {
2066
2154
  selector = selector.replace(/([+~>]) /g, "$1");
2067
2155
  let begin = 0;
2068
- const delimiter = /((?:[^\\]) +|$)/g;
2156
+ const delimiter = /((?:[^\\\u0039\u0061\u0064]) +|$)/g;
2069
2157
  return Array.from(selector.matchAll(delimiter), (match) => {
2070
2158
  const end = match.index + 1;
2071
- const part = selector.slice(begin, end);
2159
+ const part = unescapeCodepoint(selector.slice(begin, end));
2072
2160
  begin = end + 1;
2073
2161
  return new Pattern(part);
2074
2162
  });
@@ -2401,7 +2489,8 @@ class HtmlElement extends DOMNode {
2401
2489
  return this.cacheSet(ROLE, role.value);
2402
2490
  }
2403
2491
  if (this.metaElement) {
2404
- const implicitRole = this.metaElement.implicitRole(this._adapter);
2492
+ const { aria } = this.metaElement;
2493
+ const implicitRole = aria.implicitRole(this._adapter);
2405
2494
  return this.cacheSet(ROLE, implicitRole);
2406
2495
  }
2407
2496
  return this.cacheSet(ROLE, null);
@@ -3143,6 +3232,48 @@ function interpolate(text, data) {
3143
3232
  });
3144
3233
  }
3145
3234
 
3235
+ const cacheKey = Symbol("aria-naming");
3236
+ const defaultValue = "allowed";
3237
+ const prohibitedRoles = [
3238
+ "caption",
3239
+ "code",
3240
+ "deletion",
3241
+ "emphasis",
3242
+ "generic",
3243
+ "insertion",
3244
+ "paragraph",
3245
+ "presentation",
3246
+ "strong",
3247
+ "subscript",
3248
+ "superscript"
3249
+ ];
3250
+ function byRole(role) {
3251
+ return prohibitedRoles.includes(role) ? "prohibited" : "allowed";
3252
+ }
3253
+ function byMeta(element, meta) {
3254
+ return meta.aria.naming(element._adapter);
3255
+ }
3256
+ function ariaNaming(element) {
3257
+ var _a;
3258
+ const cached = element.cacheGet(cacheKey);
3259
+ if (cached) {
3260
+ return cached;
3261
+ }
3262
+ const role = (_a = element.getAttribute("role")) == null ? void 0 : _a.value;
3263
+ if (role) {
3264
+ if (role instanceof DynamicValue) {
3265
+ return element.cacheSet(cacheKey, defaultValue);
3266
+ } else {
3267
+ return element.cacheSet(cacheKey, byRole(role));
3268
+ }
3269
+ }
3270
+ const meta = element.meta;
3271
+ if (!meta) {
3272
+ return element.cacheSet(cacheKey, defaultValue);
3273
+ }
3274
+ return element.cacheSet(cacheKey, byMeta(element, meta));
3275
+ }
3276
+
3146
3277
  const patternCache = /* @__PURE__ */ new Map();
3147
3278
  function compileStringPattern(pattern) {
3148
3279
  const regexp = pattern.replace(/[*]+/g, ".+");
@@ -3661,7 +3792,7 @@ class Rule {
3661
3792
  }
3662
3793
  }
3663
3794
 
3664
- const defaults$u = {
3795
+ const defaults$v = {
3665
3796
  allowExternal: true,
3666
3797
  allowRelative: true,
3667
3798
  allowAbsolute: true,
@@ -3702,7 +3833,7 @@ function matchList(value, list) {
3702
3833
  }
3703
3834
  class AllowedLinks extends Rule {
3704
3835
  constructor(options) {
3705
- super({ ...defaults$u, ...options });
3836
+ super({ ...defaults$v, ...options });
3706
3837
  this.allowExternal = parseAllow(this.options.allowExternal);
3707
3838
  this.allowRelative = parseAllow(this.options.allowRelative);
3708
3839
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3866,7 +3997,7 @@ class AllowedLinks extends Rule {
3866
3997
  }
3867
3998
  }
3868
3999
 
3869
- const defaults$t = {
4000
+ const defaults$u = {
3870
4001
  accessible: true
3871
4002
  };
3872
4003
  function findByTarget(target, siblings) {
@@ -3896,7 +4027,7 @@ function getDescription$1(context) {
3896
4027
  }
3897
4028
  class AreaAlt extends Rule {
3898
4029
  constructor(options) {
3899
- super({ ...defaults$t, ...options });
4030
+ super({ ...defaults$u, ...options });
3900
4031
  }
3901
4032
  static schema() {
3902
4033
  return {
@@ -3975,6 +4106,9 @@ class AriaHiddenBody extends Rule {
3975
4106
  }
3976
4107
  }
3977
4108
 
4109
+ const defaults$t = {
4110
+ allowAnyNamable: false
4111
+ };
3978
4112
  const whitelisted = [
3979
4113
  "main",
3980
4114
  "nav",
@@ -4013,6 +4147,9 @@ function isValidUsage(target, meta) {
4013
4147
  return false;
4014
4148
  }
4015
4149
  class AriaLabelMisuse extends Rule {
4150
+ constructor(options) {
4151
+ super({ ...defaults$t, ...options });
4152
+ }
4016
4153
  documentation() {
4017
4154
  const valid = [
4018
4155
  "Interactive elements",
@@ -4055,6 +4192,9 @@ ${lines}`,
4055
4192
  if (isValidUsage(target, meta)) {
4056
4193
  return;
4057
4194
  }
4195
+ if (this.options.allowAnyNamable && ariaNaming(target) === "allowed") {
4196
+ return;
4197
+ }
4058
4198
  this.report(target, `"aria-label" cannot be used on this element`, attr.keyLocation);
4059
4199
  }
4060
4200
  }
@@ -6083,7 +6223,7 @@ const defaults$h = {
6083
6223
  minInitialRank: "h1",
6084
6224
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
6085
6225
  };
6086
- function isRelevant$4(event) {
6226
+ function isRelevant$5(event) {
6087
6227
  const node = event.target;
6088
6228
  return Boolean(node.meta && node.meta.heading);
6089
6229
  }
@@ -6150,7 +6290,7 @@ class HeadingLevel extends Rule {
6150
6290
  };
6151
6291
  }
6152
6292
  setup() {
6153
- this.on("tag:start", isRelevant$4, (event) => {
6293
+ this.on("tag:start", isRelevant$5, (event) => {
6154
6294
  this.onTagStart(event);
6155
6295
  });
6156
6296
  this.on("tag:ready", (event) => {
@@ -6790,7 +6930,7 @@ class MapDupName extends Rule {
6790
6930
  }
6791
6931
  }
6792
6932
 
6793
- function isRelevant$3(event) {
6933
+ function isRelevant$4(event) {
6794
6934
  return event.target.is("map");
6795
6935
  }
6796
6936
  function hasStaticValue(attr) {
@@ -6804,7 +6944,7 @@ class MapIdName extends Rule {
6804
6944
  };
6805
6945
  }
6806
6946
  setup() {
6807
- this.on("tag:ready", isRelevant$3, (event) => {
6947
+ this.on("tag:ready", isRelevant$4, (event) => {
6808
6948
  const { target } = event;
6809
6949
  const id = target.getAttribute("id");
6810
6950
  const name = target.getAttribute("name");
@@ -7058,7 +7198,7 @@ class NoDupID extends Rule {
7058
7198
  const { document } = event;
7059
7199
  const existing = /* @__PURE__ */ new Set();
7060
7200
  const elements = document.querySelectorAll("[id]");
7061
- const relevant = elements.filter(isRelevant$2);
7201
+ const relevant = elements.filter(isRelevant$3);
7062
7202
  for (const el of relevant) {
7063
7203
  const attr = el.getAttribute("id");
7064
7204
  if (!(attr == null ? void 0 : attr.value)) {
@@ -7073,7 +7213,7 @@ class NoDupID extends Rule {
7073
7213
  });
7074
7214
  }
7075
7215
  }
7076
- function isRelevant$2(element) {
7216
+ function isRelevant$3(element) {
7077
7217
  const attr = element.getAttribute("id");
7078
7218
  if (!attr) {
7079
7219
  return false;
@@ -7087,14 +7227,14 @@ function isRelevant$2(element) {
7087
7227
  return true;
7088
7228
  }
7089
7229
 
7090
- function isRelevant$1(event) {
7230
+ function isRelevant$2(event) {
7091
7231
  return event.target.is("button");
7092
7232
  }
7093
7233
  class NoImplicitButtonType extends Rule {
7094
7234
  documentation() {
7095
7235
  return {
7096
7236
  description: [
7097
- "`<button>` is missing required `type` attribute",
7237
+ "`<button>` is missing recommended `type` attribute",
7098
7238
  "",
7099
7239
  "When the `type` attribute is omitted it defaults to `submit`.",
7100
7240
  "Submit buttons are triggered when a keyboard user presses <kbd>Enter</kbd>.",
@@ -7108,6 +7248,30 @@ class NoImplicitButtonType extends Rule {
7108
7248
  url: "https://html-validate.org/rules/no-implicit-button-type.html"
7109
7249
  };
7110
7250
  }
7251
+ setup() {
7252
+ this.on("element:ready", isRelevant$2, (event) => {
7253
+ const { target } = event;
7254
+ const attr = target.getAttribute("type");
7255
+ if (!attr) {
7256
+ this.report({
7257
+ node: event.target,
7258
+ message: `<button> is missing recommended "type" attribute`
7259
+ });
7260
+ }
7261
+ });
7262
+ }
7263
+ }
7264
+
7265
+ function isRelevant$1(event) {
7266
+ return event.target.is("input");
7267
+ }
7268
+ class NoImplicitInputType extends Rule {
7269
+ documentation() {
7270
+ return {
7271
+ description: ["`<input>` is missing recommended `type` attribute"].join("\n"),
7272
+ url: "https://html-validate.org/rules/no-implicit-input-type.html"
7273
+ };
7274
+ }
7111
7275
  setup() {
7112
7276
  this.on("element:ready", isRelevant$1, (event) => {
7113
7277
  const { target } = event;
@@ -7115,7 +7279,7 @@ class NoImplicitButtonType extends Rule {
7115
7279
  if (!attr) {
7116
7280
  this.report({
7117
7281
  node: event.target,
7118
- message: `<button> is missing required "type" attribute`
7282
+ message: `<input> is missing recommended "type" attribute`
7119
7283
  });
7120
7284
  }
7121
7285
  });
@@ -7528,7 +7692,7 @@ class NoRedundantRole extends Rule {
7528
7692
  if (!meta) {
7529
7693
  return;
7530
7694
  }
7531
- const implicitRole = meta.implicitRole(target._adapter);
7695
+ const implicitRole = meta.aria.implicitRole(target._adapter);
7532
7696
  if (!implicitRole) {
7533
7697
  return;
7534
7698
  }
@@ -9271,6 +9435,7 @@ const bundledRules = {
9271
9435
  "no-dup-class": NoDupClass,
9272
9436
  "no-dup-id": NoDupID,
9273
9437
  "no-implicit-button-type": NoImplicitButtonType,
9438
+ "no-implicit-input-type": NoImplicitInputType,
9274
9439
  "no-implicit-close": NoImplicitClose,
9275
9440
  "no-inline-style": NoInlineStyle,
9276
9441
  "no-missing-references": NoMissingReferences,
@@ -9309,7 +9474,7 @@ const config$4 = {
9309
9474
  rules: {
9310
9475
  "area-alt": ["error", { accessible: true }],
9311
9476
  "aria-hidden-body": "error",
9312
- "aria-label-misuse": "error",
9477
+ "aria-label-misuse": ["error", { allowAnyNamable: false }],
9313
9478
  "deprecated-rule": "warn",
9314
9479
  "empty-heading": "error",
9315
9480
  "empty-title": "error",
@@ -9361,7 +9526,7 @@ const config$1 = {
9361
9526
  rules: {
9362
9527
  "area-alt": ["error", { accessible: true }],
9363
9528
  "aria-hidden-body": "error",
9364
- "aria-label-misuse": "error",
9529
+ "aria-label-misuse": ["error", { allowAnyNamable: false }],
9365
9530
  "attr-case": "error",
9366
9531
  "attr-delimiter": "error",
9367
9532
  "attr-quotes": "error",
@@ -9402,6 +9567,7 @@ const config$1 = {
9402
9567
  "no-dup-class": "error",
9403
9568
  "no-dup-id": "error",
9404
9569
  "no-implicit-button-type": "error",
9570
+ "no-implicit-input-type": "error",
9405
9571
  "no-implicit-close": "error",
9406
9572
  "no-inline-style": "error",
9407
9573
  "no-multiple-main": "error",
@@ -9441,6 +9607,7 @@ var recommended = config$1;
9441
9607
  const config = {
9442
9608
  rules: {
9443
9609
  "area-alt": ["error", { accessible: false }],
9610
+ "aria-label-misuse": ["error", { allowAnyNamable: true }],
9444
9611
  "attr-spacing": "error",
9445
9612
  "attribute-allowed-values": "error",
9446
9613
  "attribute-misuse": "error",
@@ -11693,7 +11860,7 @@ class HtmlValidate {
11693
11860
  }
11694
11861
 
11695
11862
  const name = "html-validate";
11696
- const version = "8.9.1";
11863
+ const version = "8.11.0";
11697
11864
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11698
11865
 
11699
11866
  function definePlugin(plugin) {
@@ -11769,6 +11936,14 @@ const cleanRangeBackSlash = slashes => {
11769
11936
  // '`foo/`' should not continue with the '`..`'
11770
11937
  const REPLACERS = [
11771
11938
 
11939
+ [
11940
+ // remove BOM
11941
+ // TODO:
11942
+ // Other similar zero-width characters?
11943
+ /^\uFEFF/,
11944
+ () => EMPTY
11945
+ ],
11946
+
11772
11947
  // > Trailing spaces are ignored unless they are quoted with backslash ("\")
11773
11948
  [
11774
11949
  // (a\ ) -> (a )
@@ -12562,5 +12737,5 @@ function compatibilityCheckImpl(name, declared, options) {
12562
12737
  return false;
12563
12738
  }
12564
12739
 
12565
- export { Attribute as A, Parser as B, Config as C, DOMNode as D, ruleExists as E, EventHandler as F, compatibilityCheckImpl as G, HtmlValidate as H, codeframe as I, name as J, bugs as K, MetaCopyableProperty as M, NodeClosed as N, Presets as P, ResolvedConfig as R, Severity as S, TextNode as T, UserError as U, Validator as V, WrappedError as W, ConfigError as a, ConfigLoader as b, defineConfig as c, deepmerge$1 as d, ensureError as e, StaticConfigLoader as f, getFormatter as g, DOMTokenList as h, ignore$1 as i, DOMTree as j, DynamicValue as k, HtmlElement as l, NodeType as m, NestedError as n, SchemaValidationError as o, MetaTable as p, TextContent$1 as q, Rule as r, staticResolver as s, TextClassification as t, classifyNodeText as u, version as v, keywordPatternMatcher as w, sliceLocation as x, Reporter as y, definePlugin as z };
12740
+ export { Attribute as A, definePlugin as B, Config as C, DOMNode as D, Parser as E, ruleExists as F, EventHandler as G, HtmlValidate as H, compatibilityCheckImpl as I, codeframe as J, name as K, bugs as L, MetaCopyableProperty as M, NodeClosed as N, Presets as P, ResolvedConfig as R, Severity as S, TextNode as T, UserError as U, Validator as V, WrappedError as W, ConfigError as a, ConfigLoader as b, defineConfig as c, deepmerge$1 as d, ensureError as e, StaticConfigLoader as f, getFormatter as g, DOMTokenList as h, ignore$1 as i, DOMTree as j, DynamicValue as k, HtmlElement as l, NodeType as m, NestedError as n, SchemaValidationError as o, MetaTable as p, TextContent$1 as q, Rule as r, staticResolver as s, ariaNaming as t, TextClassification as u, version as v, classifyNodeText as w, keywordPatternMatcher as x, sliceLocation as y, Reporter as z };
12566
12741
  //# sourceMappingURL=core.js.map