html-validate 11.2.0 → 11.4.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/cjs/core.js +184 -39
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/elements.js +127 -3
- package/dist/cjs/elements.js.map +1 -1
- package/dist/esm/core.js +184 -39
- package/dist/esm/core.js.map +1 -1
- package/dist/esm/elements.js +127 -3
- package/dist/esm/elements.js.map +1 -1
- package/dist/schema/elements.json +5 -0
- package/dist/types/browser.d.ts +23 -0
- package/dist/types/index.d.ts +23 -0
- package/package.json +1 -1
package/dist/esm/core.js
CHANGED
|
@@ -897,6 +897,13 @@ const definitions = {
|
|
|
897
897
|
type: "boolean",
|
|
898
898
|
title: "Set to true if this attribute can optionally omit its value"
|
|
899
899
|
},
|
|
900
|
+
reference: {
|
|
901
|
+
type: "string",
|
|
902
|
+
"enum": [
|
|
903
|
+
"id"
|
|
904
|
+
],
|
|
905
|
+
title: "Set when the attribute references another element."
|
|
906
|
+
},
|
|
900
907
|
required: {
|
|
901
908
|
title: "Set to true or a function to evaluate if this attribute is required",
|
|
902
909
|
oneOf: [
|
|
@@ -1066,6 +1073,7 @@ const MetaCopyableProperty = [
|
|
|
1066
1073
|
"labelable",
|
|
1067
1074
|
"submitButton",
|
|
1068
1075
|
"attributes",
|
|
1076
|
+
"patternAttributes",
|
|
1069
1077
|
"aria",
|
|
1070
1078
|
"permittedContent",
|
|
1071
1079
|
"permittedDescendants",
|
|
@@ -1078,6 +1086,54 @@ function setMetaProperty(dst, key, value) {
|
|
|
1078
1086
|
dst[key] = value;
|
|
1079
1087
|
}
|
|
1080
1088
|
|
|
1089
|
+
const SYNTAX_CHARACTERS = /[$()*+.?[\\\]^{|}]/;
|
|
1090
|
+
const CONTROL_ESCAPES = /* @__PURE__ */ new Map([
|
|
1091
|
+
[" ", "t"],
|
|
1092
|
+
["\n", "n"],
|
|
1093
|
+
["\v", "v"],
|
|
1094
|
+
["\f", "f"],
|
|
1095
|
+
["\r", "r"]
|
|
1096
|
+
]);
|
|
1097
|
+
const OTHER_PUNCTUATORS = /^[!"#%&',:;<=>@`~-]$/;
|
|
1098
|
+
const WHITE_SPACE = /^[\t\v\f\uFEFF\p{Zs}]$/u;
|
|
1099
|
+
const LINE_TERMINATOR = /^[\n\r\u2028\u2029]$/;
|
|
1100
|
+
const SURROGATE = /^[\uD800-\uDFFF]$/;
|
|
1101
|
+
function isDecimalDigitOrASCIILetter(ch) {
|
|
1102
|
+
return /^[\dA-Za-z]$/.test(ch);
|
|
1103
|
+
}
|
|
1104
|
+
function needEscape(ch) {
|
|
1105
|
+
return OTHER_PUNCTUATORS.test(ch) || WHITE_SPACE.test(ch) || LINE_TERMINATOR.test(ch) || SURROGATE.test(ch);
|
|
1106
|
+
}
|
|
1107
|
+
function unicodeEscape(ch) {
|
|
1108
|
+
return `\\u${ch.codePointAt(0).toString(16).padStart(4, "0")}`;
|
|
1109
|
+
}
|
|
1110
|
+
function encodeForRegExpEscape(ch) {
|
|
1111
|
+
if (SYNTAX_CHARACTERS.test(ch) || ch === "/") {
|
|
1112
|
+
return `\\${ch}`;
|
|
1113
|
+
}
|
|
1114
|
+
if (CONTROL_ESCAPES.has(ch)) {
|
|
1115
|
+
return `\\${CONTROL_ESCAPES.get(ch)}`;
|
|
1116
|
+
}
|
|
1117
|
+
if (needEscape(ch)) {
|
|
1118
|
+
if (/[\u0000-\u00FF]/.test(ch)) {
|
|
1119
|
+
return `\\x${ch.codePointAt(0).toString(16).padStart(2, "0")}`;
|
|
1120
|
+
}
|
|
1121
|
+
return ch.split("").map((c) => unicodeEscape(c)).join("");
|
|
1122
|
+
}
|
|
1123
|
+
return ch;
|
|
1124
|
+
}
|
|
1125
|
+
function regexpEscape(str) {
|
|
1126
|
+
let escaped = "";
|
|
1127
|
+
for (const c of str) {
|
|
1128
|
+
if (escaped === "" && isDecimalDigitOrASCIILetter(c)) {
|
|
1129
|
+
escaped += `\\x${c.codePointAt(0).toString(16).padStart(2, "0")}`;
|
|
1130
|
+
} else {
|
|
1131
|
+
escaped += encodeForRegExpEscape(c);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return escaped;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1081
1137
|
function isSet(value) {
|
|
1082
1138
|
return value !== void 0;
|
|
1083
1139
|
}
|
|
@@ -1115,18 +1171,35 @@ function migrateSingleAttribute(src, key) {
|
|
|
1115
1171
|
return stripUndefined({ ...result, ...attr });
|
|
1116
1172
|
}
|
|
1117
1173
|
}
|
|
1174
|
+
function isPatternAttribute$1(key) {
|
|
1175
|
+
return key.includes("*");
|
|
1176
|
+
}
|
|
1177
|
+
function patternToRegex(pattern) {
|
|
1178
|
+
const escaped = pattern.split("*").map(regexpEscape).join(".+");
|
|
1179
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
1180
|
+
}
|
|
1118
1181
|
function migrateAttributes(src) {
|
|
1119
1182
|
const keys = [
|
|
1120
1183
|
...Object.keys(src.attributes ?? {}),
|
|
1121
1184
|
...src.requiredAttributes ?? [],
|
|
1122
1185
|
...src.deprecatedAttributes ?? []
|
|
1123
|
-
|
|
1124
|
-
].toSorted();
|
|
1186
|
+
].filter((key) => !isPatternAttribute$1(key)).toSorted();
|
|
1125
1187
|
const entries = keys.map((key) => {
|
|
1126
1188
|
return [key, migrateSingleAttribute(src, key)];
|
|
1127
1189
|
});
|
|
1128
1190
|
return Object.fromEntries(entries);
|
|
1129
1191
|
}
|
|
1192
|
+
function migratePatternAttributes(src) {
|
|
1193
|
+
const attrs = src.attributes ?? {};
|
|
1194
|
+
return Object.entries(attrs).filter(([key]) => isPatternAttribute$1(key)).map(([pattern]) => {
|
|
1195
|
+
const { delete: deleted, ...attr } = migrateSingleAttribute(src, pattern);
|
|
1196
|
+
const regexp = patternToRegex(pattern);
|
|
1197
|
+
if (deleted) {
|
|
1198
|
+
return { pattern, regexp, delete: true };
|
|
1199
|
+
}
|
|
1200
|
+
return { ...attr, pattern, regexp };
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1130
1203
|
function normalizeAriaImplicitRole(value) {
|
|
1131
1204
|
if (!value) {
|
|
1132
1205
|
return () => null;
|
|
@@ -1151,6 +1224,7 @@ function migrateElement(src) {
|
|
|
1151
1224
|
...src,
|
|
1152
1225
|
formAssociated: void 0,
|
|
1153
1226
|
attributes: migrateAttributes(src),
|
|
1227
|
+
patternAttributes: migratePatternAttributes(src),
|
|
1154
1228
|
textContent: src.textContent,
|
|
1155
1229
|
focusable: src.focusable ?? false,
|
|
1156
1230
|
implicitRole,
|
|
@@ -1380,6 +1454,18 @@ class MetaTable {
|
|
|
1380
1454
|
}
|
|
1381
1455
|
}
|
|
1382
1456
|
merged.attributes = mergedAttrs;
|
|
1457
|
+
const mergedPatterns = [
|
|
1458
|
+
...b.patternAttributes,
|
|
1459
|
+
...a.patternAttributes ?? []
|
|
1460
|
+
];
|
|
1461
|
+
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1462
|
+
merged.patternAttributes = mergedPatterns.filter((entry) => {
|
|
1463
|
+
if (seenPatterns.has(entry.pattern)) {
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
seenPatterns.add(entry.pattern);
|
|
1467
|
+
return !entry.delete;
|
|
1468
|
+
});
|
|
1383
1469
|
if (a.aria) {
|
|
1384
1470
|
merged.aria = { ...a.aria, ...b.aria };
|
|
1385
1471
|
}
|
|
@@ -3457,6 +3543,10 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
|
|
|
3457
3543
|
return false;
|
|
3458
3544
|
}
|
|
3459
3545
|
|
|
3546
|
+
function isPatternAttribute(attrName, dynamicAttributes) {
|
|
3547
|
+
return dynamicAttributes.some((entry) => entry.regexp.test(attrName));
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3460
3550
|
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
3461
3551
|
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
3462
3552
|
const INERT_CACHE = Symbol(isInert.name);
|
|
@@ -7459,10 +7549,58 @@ class IdPattern extends BasePatternRule {
|
|
|
7459
7549
|
|
|
7460
7550
|
const restricted = /* @__PURE__ */ new Map([
|
|
7461
7551
|
["accept", ["file"]],
|
|
7552
|
+
["alpha", ["color"]],
|
|
7462
7553
|
["alt", ["image"]],
|
|
7554
|
+
[
|
|
7555
|
+
"autocapitalize",
|
|
7556
|
+
[
|
|
7557
|
+
"button",
|
|
7558
|
+
"checkbox",
|
|
7559
|
+
"color",
|
|
7560
|
+
"date",
|
|
7561
|
+
"datetime-local",
|
|
7562
|
+
"file",
|
|
7563
|
+
"hidden",
|
|
7564
|
+
"image",
|
|
7565
|
+
"month",
|
|
7566
|
+
"number",
|
|
7567
|
+
"radio",
|
|
7568
|
+
"range",
|
|
7569
|
+
"reset",
|
|
7570
|
+
"search",
|
|
7571
|
+
"submit",
|
|
7572
|
+
"tel",
|
|
7573
|
+
"text",
|
|
7574
|
+
"time",
|
|
7575
|
+
"week"
|
|
7576
|
+
]
|
|
7577
|
+
],
|
|
7578
|
+
[
|
|
7579
|
+
"autocomplete",
|
|
7580
|
+
[
|
|
7581
|
+
"color",
|
|
7582
|
+
"date",
|
|
7583
|
+
"datetime-local",
|
|
7584
|
+
"email",
|
|
7585
|
+
"file",
|
|
7586
|
+
"hidden",
|
|
7587
|
+
"image",
|
|
7588
|
+
"month",
|
|
7589
|
+
"number",
|
|
7590
|
+
"password",
|
|
7591
|
+
"range",
|
|
7592
|
+
"search",
|
|
7593
|
+
"tel",
|
|
7594
|
+
"text",
|
|
7595
|
+
"time",
|
|
7596
|
+
"url",
|
|
7597
|
+
"week"
|
|
7598
|
+
]
|
|
7599
|
+
],
|
|
7463
7600
|
["capture", ["file"]],
|
|
7464
7601
|
["checked", ["checkbox", "radio"]],
|
|
7465
|
-
["
|
|
7602
|
+
["colorspace", ["color"]],
|
|
7603
|
+
["dirname", ["hidden", "text", "search", "url", "tel", "email"]],
|
|
7466
7604
|
["height", ["image"]],
|
|
7467
7605
|
[
|
|
7468
7606
|
"list",
|
|
@@ -7489,6 +7627,8 @@ const restricted = /* @__PURE__ */ new Map([
|
|
|
7489
7627
|
["multiple", ["email", "file"]],
|
|
7490
7628
|
["pattern", ["text", "search", "url", "tel", "email", "password"]],
|
|
7491
7629
|
["placeholder", ["text", "search", "url", "tel", "email", "password", "number"]],
|
|
7630
|
+
["popovertarget", ["button"]],
|
|
7631
|
+
["popovertargetaction", ["button"]],
|
|
7492
7632
|
[
|
|
7493
7633
|
"readonly",
|
|
7494
7634
|
[
|
|
@@ -7529,6 +7669,31 @@ const restricted = /* @__PURE__ */ new Map([
|
|
|
7529
7669
|
["size", ["text", "search", "url", "tel", "email", "password"]],
|
|
7530
7670
|
["src", ["image"]],
|
|
7531
7671
|
["step", ["date", "month", "week", "time", "datetime-local", "number", "range"]],
|
|
7672
|
+
[
|
|
7673
|
+
"value",
|
|
7674
|
+
[
|
|
7675
|
+
"button",
|
|
7676
|
+
"checkbox",
|
|
7677
|
+
"color",
|
|
7678
|
+
"date",
|
|
7679
|
+
"datetime-local",
|
|
7680
|
+
"email",
|
|
7681
|
+
"hidden",
|
|
7682
|
+
"month",
|
|
7683
|
+
"number",
|
|
7684
|
+
"password",
|
|
7685
|
+
"radio",
|
|
7686
|
+
"range",
|
|
7687
|
+
"reset",
|
|
7688
|
+
"search",
|
|
7689
|
+
"submit",
|
|
7690
|
+
"tel",
|
|
7691
|
+
"text",
|
|
7692
|
+
"time",
|
|
7693
|
+
"url",
|
|
7694
|
+
"week"
|
|
7695
|
+
]
|
|
7696
|
+
],
|
|
7532
7697
|
["width", ["image"]]
|
|
7533
7698
|
]);
|
|
7534
7699
|
function isInput(event) {
|
|
@@ -8520,16 +8685,6 @@ class NoInlineStyle extends Rule {
|
|
|
8520
8685
|
}
|
|
8521
8686
|
}
|
|
8522
8687
|
|
|
8523
|
-
const ARIA = [
|
|
8524
|
-
{ property: "aria-activedescendant", isList: false },
|
|
8525
|
-
{ property: "aria-controls", isList: true },
|
|
8526
|
-
{ property: "aria-describedby", isList: true },
|
|
8527
|
-
{ property: "aria-details", isList: false },
|
|
8528
|
-
{ property: "aria-errormessage", isList: false },
|
|
8529
|
-
{ property: "aria-flowto", isList: true },
|
|
8530
|
-
{ property: "aria-labelledby", isList: true },
|
|
8531
|
-
{ property: "aria-owns", isList: true }
|
|
8532
|
-
];
|
|
8533
8688
|
function idMissing(document, id) {
|
|
8534
8689
|
const nodes = document.querySelectorAll(generateIdSelector(id));
|
|
8535
8690
|
return nodes.length === 0;
|
|
@@ -8544,20 +8699,18 @@ class NoMissingReferences extends Rule {
|
|
|
8544
8699
|
setup() {
|
|
8545
8700
|
this.on("dom:ready", (event) => {
|
|
8546
8701
|
const document = event.document;
|
|
8547
|
-
|
|
8548
|
-
const
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
for (const node of document.querySelectorAll("input[list]")) {
|
|
8552
|
-
const attr = node.getAttribute("list");
|
|
8553
|
-
this.validateReference(document, node, attr, false);
|
|
8554
|
-
}
|
|
8555
|
-
for (const { property, isList } of ARIA) {
|
|
8556
|
-
for (const node of document.querySelectorAll(`[${property}]`)) {
|
|
8557
|
-
const attr = node.getAttribute(property);
|
|
8558
|
-
this.validateReference(document, node, attr, isList);
|
|
8702
|
+
walk.depthFirst(document, (node) => {
|
|
8703
|
+
const meta = node.meta;
|
|
8704
|
+
if (!meta?.attributes) {
|
|
8705
|
+
return;
|
|
8559
8706
|
}
|
|
8560
|
-
|
|
8707
|
+
for (const attr of node.attributes) {
|
|
8708
|
+
const attrMeta = meta.attributes[attr.key];
|
|
8709
|
+
if (attrMeta?.reference === "id") {
|
|
8710
|
+
this.validateReference(document, node, attr, attrMeta.list ?? false);
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8713
|
+
});
|
|
8561
8714
|
});
|
|
8562
8715
|
}
|
|
8563
8716
|
validateReference(document, node, attr, isList) {
|
|
@@ -8959,18 +9112,7 @@ class NoTrailingWhitespace extends Rule {
|
|
|
8959
9112
|
}
|
|
8960
9113
|
}
|
|
8961
9114
|
|
|
8962
|
-
const skipPatterns = [
|
|
8963
|
-
/^data-/i,
|
|
8964
|
-
/^aria-/i,
|
|
8965
|
-
/^on[a-z]/i,
|
|
8966
|
-
/^xml(ns)?:/i,
|
|
8967
|
-
/^:/,
|
|
8968
|
-
/^@/,
|
|
8969
|
-
/^ng-/i,
|
|
8970
|
-
/^v-/i,
|
|
8971
|
-
/^x-/i,
|
|
8972
|
-
/^\[/
|
|
8973
|
-
];
|
|
9115
|
+
const skipPatterns = [/^:/, /^@/, /^ng-/i, /^v-/i, /^x-/i, /^\[/, /^#/];
|
|
8974
9116
|
function isKnownDynamicAttr(attr) {
|
|
8975
9117
|
return skipPatterns.some((pattern) => pattern.test(attr));
|
|
8976
9118
|
}
|
|
@@ -8992,6 +9134,9 @@ class NoUnknownAttributes extends Rule {
|
|
|
8992
9134
|
if (attr in meta.attributes) {
|
|
8993
9135
|
return;
|
|
8994
9136
|
}
|
|
9137
|
+
if (isPatternAttribute(attr, meta.patternAttributes)) {
|
|
9138
|
+
return;
|
|
9139
|
+
}
|
|
8995
9140
|
if (isKnownDynamicAttr(attr)) {
|
|
8996
9141
|
return;
|
|
8997
9142
|
}
|
|
@@ -12630,7 +12775,7 @@ class EventHandler {
|
|
|
12630
12775
|
}
|
|
12631
12776
|
|
|
12632
12777
|
const name = "html-validate";
|
|
12633
|
-
const version = "11.
|
|
12778
|
+
const version = "11.4.0";
|
|
12634
12779
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
12635
12780
|
|
|
12636
12781
|
function freeze(src) {
|