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/cjs/core.js
CHANGED
|
@@ -906,6 +906,13 @@ const definitions = {
|
|
|
906
906
|
type: "boolean",
|
|
907
907
|
title: "Set to true if this attribute can optionally omit its value"
|
|
908
908
|
},
|
|
909
|
+
reference: {
|
|
910
|
+
type: "string",
|
|
911
|
+
"enum": [
|
|
912
|
+
"id"
|
|
913
|
+
],
|
|
914
|
+
title: "Set when the attribute references another element."
|
|
915
|
+
},
|
|
909
916
|
required: {
|
|
910
917
|
title: "Set to true or a function to evaluate if this attribute is required",
|
|
911
918
|
oneOf: [
|
|
@@ -1075,6 +1082,7 @@ const MetaCopyableProperty = [
|
|
|
1075
1082
|
"labelable",
|
|
1076
1083
|
"submitButton",
|
|
1077
1084
|
"attributes",
|
|
1085
|
+
"patternAttributes",
|
|
1078
1086
|
"aria",
|
|
1079
1087
|
"permittedContent",
|
|
1080
1088
|
"permittedDescendants",
|
|
@@ -1087,6 +1095,54 @@ function setMetaProperty(dst, key, value) {
|
|
|
1087
1095
|
dst[key] = value;
|
|
1088
1096
|
}
|
|
1089
1097
|
|
|
1098
|
+
const SYNTAX_CHARACTERS = /[$()*+.?[\\\]^{|}]/;
|
|
1099
|
+
const CONTROL_ESCAPES = /* @__PURE__ */ new Map([
|
|
1100
|
+
[" ", "t"],
|
|
1101
|
+
["\n", "n"],
|
|
1102
|
+
["\v", "v"],
|
|
1103
|
+
["\f", "f"],
|
|
1104
|
+
["\r", "r"]
|
|
1105
|
+
]);
|
|
1106
|
+
const OTHER_PUNCTUATORS = /^[!"#%&',:;<=>@`~-]$/;
|
|
1107
|
+
const WHITE_SPACE = /^[\t\v\f\uFEFF\p{Zs}]$/u;
|
|
1108
|
+
const LINE_TERMINATOR = /^[\n\r\u2028\u2029]$/;
|
|
1109
|
+
const SURROGATE = /^[\uD800-\uDFFF]$/;
|
|
1110
|
+
function isDecimalDigitOrASCIILetter(ch) {
|
|
1111
|
+
return /^[\dA-Za-z]$/.test(ch);
|
|
1112
|
+
}
|
|
1113
|
+
function needEscape(ch) {
|
|
1114
|
+
return OTHER_PUNCTUATORS.test(ch) || WHITE_SPACE.test(ch) || LINE_TERMINATOR.test(ch) || SURROGATE.test(ch);
|
|
1115
|
+
}
|
|
1116
|
+
function unicodeEscape(ch) {
|
|
1117
|
+
return `\\u${ch.codePointAt(0).toString(16).padStart(4, "0")}`;
|
|
1118
|
+
}
|
|
1119
|
+
function encodeForRegExpEscape(ch) {
|
|
1120
|
+
if (SYNTAX_CHARACTERS.test(ch) || ch === "/") {
|
|
1121
|
+
return `\\${ch}`;
|
|
1122
|
+
}
|
|
1123
|
+
if (CONTROL_ESCAPES.has(ch)) {
|
|
1124
|
+
return `\\${CONTROL_ESCAPES.get(ch)}`;
|
|
1125
|
+
}
|
|
1126
|
+
if (needEscape(ch)) {
|
|
1127
|
+
if (/[\u0000-\u00FF]/.test(ch)) {
|
|
1128
|
+
return `\\x${ch.codePointAt(0).toString(16).padStart(2, "0")}`;
|
|
1129
|
+
}
|
|
1130
|
+
return ch.split("").map((c) => unicodeEscape(c)).join("");
|
|
1131
|
+
}
|
|
1132
|
+
return ch;
|
|
1133
|
+
}
|
|
1134
|
+
function regexpEscape(str) {
|
|
1135
|
+
let escaped = "";
|
|
1136
|
+
for (const c of str) {
|
|
1137
|
+
if (escaped === "" && isDecimalDigitOrASCIILetter(c)) {
|
|
1138
|
+
escaped += `\\x${c.codePointAt(0).toString(16).padStart(2, "0")}`;
|
|
1139
|
+
} else {
|
|
1140
|
+
escaped += encodeForRegExpEscape(c);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return escaped;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1090
1146
|
function isSet(value) {
|
|
1091
1147
|
return value !== void 0;
|
|
1092
1148
|
}
|
|
@@ -1124,18 +1180,35 @@ function migrateSingleAttribute(src, key) {
|
|
|
1124
1180
|
return stripUndefined({ ...result, ...attr });
|
|
1125
1181
|
}
|
|
1126
1182
|
}
|
|
1183
|
+
function isPatternAttribute$1(key) {
|
|
1184
|
+
return key.includes("*");
|
|
1185
|
+
}
|
|
1186
|
+
function patternToRegex(pattern) {
|
|
1187
|
+
const escaped = pattern.split("*").map(regexpEscape).join(".+");
|
|
1188
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
1189
|
+
}
|
|
1127
1190
|
function migrateAttributes(src) {
|
|
1128
1191
|
const keys = [
|
|
1129
1192
|
...Object.keys(src.attributes ?? {}),
|
|
1130
1193
|
...src.requiredAttributes ?? [],
|
|
1131
1194
|
...src.deprecatedAttributes ?? []
|
|
1132
|
-
|
|
1133
|
-
].toSorted();
|
|
1195
|
+
].filter((key) => !isPatternAttribute$1(key)).toSorted();
|
|
1134
1196
|
const entries = keys.map((key) => {
|
|
1135
1197
|
return [key, migrateSingleAttribute(src, key)];
|
|
1136
1198
|
});
|
|
1137
1199
|
return Object.fromEntries(entries);
|
|
1138
1200
|
}
|
|
1201
|
+
function migratePatternAttributes(src) {
|
|
1202
|
+
const attrs = src.attributes ?? {};
|
|
1203
|
+
return Object.entries(attrs).filter(([key]) => isPatternAttribute$1(key)).map(([pattern]) => {
|
|
1204
|
+
const { delete: deleted, ...attr } = migrateSingleAttribute(src, pattern);
|
|
1205
|
+
const regexp = patternToRegex(pattern);
|
|
1206
|
+
if (deleted) {
|
|
1207
|
+
return { pattern, regexp, delete: true };
|
|
1208
|
+
}
|
|
1209
|
+
return { ...attr, pattern, regexp };
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1139
1212
|
function normalizeAriaImplicitRole(value) {
|
|
1140
1213
|
if (!value) {
|
|
1141
1214
|
return () => null;
|
|
@@ -1160,6 +1233,7 @@ function migrateElement(src) {
|
|
|
1160
1233
|
...src,
|
|
1161
1234
|
formAssociated: void 0,
|
|
1162
1235
|
attributes: migrateAttributes(src),
|
|
1236
|
+
patternAttributes: migratePatternAttributes(src),
|
|
1163
1237
|
textContent: src.textContent,
|
|
1164
1238
|
focusable: src.focusable ?? false,
|
|
1165
1239
|
implicitRole,
|
|
@@ -1389,6 +1463,18 @@ class MetaTable {
|
|
|
1389
1463
|
}
|
|
1390
1464
|
}
|
|
1391
1465
|
merged.attributes = mergedAttrs;
|
|
1466
|
+
const mergedPatterns = [
|
|
1467
|
+
...b.patternAttributes,
|
|
1468
|
+
...a.patternAttributes ?? []
|
|
1469
|
+
];
|
|
1470
|
+
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1471
|
+
merged.patternAttributes = mergedPatterns.filter((entry) => {
|
|
1472
|
+
if (seenPatterns.has(entry.pattern)) {
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
seenPatterns.add(entry.pattern);
|
|
1476
|
+
return !entry.delete;
|
|
1477
|
+
});
|
|
1392
1478
|
if (a.aria) {
|
|
1393
1479
|
merged.aria = { ...a.aria, ...b.aria };
|
|
1394
1480
|
}
|
|
@@ -3466,6 +3552,10 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
|
|
|
3466
3552
|
return false;
|
|
3467
3553
|
}
|
|
3468
3554
|
|
|
3555
|
+
function isPatternAttribute(attrName, dynamicAttributes) {
|
|
3556
|
+
return dynamicAttributes.some((entry) => entry.regexp.test(attrName));
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3469
3559
|
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
3470
3560
|
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
3471
3561
|
const INERT_CACHE = Symbol(isInert.name);
|
|
@@ -7468,10 +7558,58 @@ class IdPattern extends BasePatternRule {
|
|
|
7468
7558
|
|
|
7469
7559
|
const restricted = /* @__PURE__ */ new Map([
|
|
7470
7560
|
["accept", ["file"]],
|
|
7561
|
+
["alpha", ["color"]],
|
|
7471
7562
|
["alt", ["image"]],
|
|
7563
|
+
[
|
|
7564
|
+
"autocapitalize",
|
|
7565
|
+
[
|
|
7566
|
+
"button",
|
|
7567
|
+
"checkbox",
|
|
7568
|
+
"color",
|
|
7569
|
+
"date",
|
|
7570
|
+
"datetime-local",
|
|
7571
|
+
"file",
|
|
7572
|
+
"hidden",
|
|
7573
|
+
"image",
|
|
7574
|
+
"month",
|
|
7575
|
+
"number",
|
|
7576
|
+
"radio",
|
|
7577
|
+
"range",
|
|
7578
|
+
"reset",
|
|
7579
|
+
"search",
|
|
7580
|
+
"submit",
|
|
7581
|
+
"tel",
|
|
7582
|
+
"text",
|
|
7583
|
+
"time",
|
|
7584
|
+
"week"
|
|
7585
|
+
]
|
|
7586
|
+
],
|
|
7587
|
+
[
|
|
7588
|
+
"autocomplete",
|
|
7589
|
+
[
|
|
7590
|
+
"color",
|
|
7591
|
+
"date",
|
|
7592
|
+
"datetime-local",
|
|
7593
|
+
"email",
|
|
7594
|
+
"file",
|
|
7595
|
+
"hidden",
|
|
7596
|
+
"image",
|
|
7597
|
+
"month",
|
|
7598
|
+
"number",
|
|
7599
|
+
"password",
|
|
7600
|
+
"range",
|
|
7601
|
+
"search",
|
|
7602
|
+
"tel",
|
|
7603
|
+
"text",
|
|
7604
|
+
"time",
|
|
7605
|
+
"url",
|
|
7606
|
+
"week"
|
|
7607
|
+
]
|
|
7608
|
+
],
|
|
7472
7609
|
["capture", ["file"]],
|
|
7473
7610
|
["checked", ["checkbox", "radio"]],
|
|
7474
|
-
["
|
|
7611
|
+
["colorspace", ["color"]],
|
|
7612
|
+
["dirname", ["hidden", "text", "search", "url", "tel", "email"]],
|
|
7475
7613
|
["height", ["image"]],
|
|
7476
7614
|
[
|
|
7477
7615
|
"list",
|
|
@@ -7498,6 +7636,8 @@ const restricted = /* @__PURE__ */ new Map([
|
|
|
7498
7636
|
["multiple", ["email", "file"]],
|
|
7499
7637
|
["pattern", ["text", "search", "url", "tel", "email", "password"]],
|
|
7500
7638
|
["placeholder", ["text", "search", "url", "tel", "email", "password", "number"]],
|
|
7639
|
+
["popovertarget", ["button"]],
|
|
7640
|
+
["popovertargetaction", ["button"]],
|
|
7501
7641
|
[
|
|
7502
7642
|
"readonly",
|
|
7503
7643
|
[
|
|
@@ -7538,6 +7678,31 @@ const restricted = /* @__PURE__ */ new Map([
|
|
|
7538
7678
|
["size", ["text", "search", "url", "tel", "email", "password"]],
|
|
7539
7679
|
["src", ["image"]],
|
|
7540
7680
|
["step", ["date", "month", "week", "time", "datetime-local", "number", "range"]],
|
|
7681
|
+
[
|
|
7682
|
+
"value",
|
|
7683
|
+
[
|
|
7684
|
+
"button",
|
|
7685
|
+
"checkbox",
|
|
7686
|
+
"color",
|
|
7687
|
+
"date",
|
|
7688
|
+
"datetime-local",
|
|
7689
|
+
"email",
|
|
7690
|
+
"hidden",
|
|
7691
|
+
"month",
|
|
7692
|
+
"number",
|
|
7693
|
+
"password",
|
|
7694
|
+
"radio",
|
|
7695
|
+
"range",
|
|
7696
|
+
"reset",
|
|
7697
|
+
"search",
|
|
7698
|
+
"submit",
|
|
7699
|
+
"tel",
|
|
7700
|
+
"text",
|
|
7701
|
+
"time",
|
|
7702
|
+
"url",
|
|
7703
|
+
"week"
|
|
7704
|
+
]
|
|
7705
|
+
],
|
|
7541
7706
|
["width", ["image"]]
|
|
7542
7707
|
]);
|
|
7543
7708
|
function isInput(event) {
|
|
@@ -8529,16 +8694,6 @@ class NoInlineStyle extends Rule {
|
|
|
8529
8694
|
}
|
|
8530
8695
|
}
|
|
8531
8696
|
|
|
8532
|
-
const ARIA = [
|
|
8533
|
-
{ property: "aria-activedescendant", isList: false },
|
|
8534
|
-
{ property: "aria-controls", isList: true },
|
|
8535
|
-
{ property: "aria-describedby", isList: true },
|
|
8536
|
-
{ property: "aria-details", isList: false },
|
|
8537
|
-
{ property: "aria-errormessage", isList: false },
|
|
8538
|
-
{ property: "aria-flowto", isList: true },
|
|
8539
|
-
{ property: "aria-labelledby", isList: true },
|
|
8540
|
-
{ property: "aria-owns", isList: true }
|
|
8541
|
-
];
|
|
8542
8697
|
function idMissing(document, id) {
|
|
8543
8698
|
const nodes = document.querySelectorAll(generateIdSelector(id));
|
|
8544
8699
|
return nodes.length === 0;
|
|
@@ -8553,20 +8708,18 @@ class NoMissingReferences extends Rule {
|
|
|
8553
8708
|
setup() {
|
|
8554
8709
|
this.on("dom:ready", (event) => {
|
|
8555
8710
|
const document = event.document;
|
|
8556
|
-
|
|
8557
|
-
const
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
for (const node of document.querySelectorAll("input[list]")) {
|
|
8561
|
-
const attr = node.getAttribute("list");
|
|
8562
|
-
this.validateReference(document, node, attr, false);
|
|
8563
|
-
}
|
|
8564
|
-
for (const { property, isList } of ARIA) {
|
|
8565
|
-
for (const node of document.querySelectorAll(`[${property}]`)) {
|
|
8566
|
-
const attr = node.getAttribute(property);
|
|
8567
|
-
this.validateReference(document, node, attr, isList);
|
|
8711
|
+
walk.depthFirst(document, (node) => {
|
|
8712
|
+
const meta = node.meta;
|
|
8713
|
+
if (!meta?.attributes) {
|
|
8714
|
+
return;
|
|
8568
8715
|
}
|
|
8569
|
-
|
|
8716
|
+
for (const attr of node.attributes) {
|
|
8717
|
+
const attrMeta = meta.attributes[attr.key];
|
|
8718
|
+
if (attrMeta?.reference === "id") {
|
|
8719
|
+
this.validateReference(document, node, attr, attrMeta.list ?? false);
|
|
8720
|
+
}
|
|
8721
|
+
}
|
|
8722
|
+
});
|
|
8570
8723
|
});
|
|
8571
8724
|
}
|
|
8572
8725
|
validateReference(document, node, attr, isList) {
|
|
@@ -8968,18 +9121,7 @@ class NoTrailingWhitespace extends Rule {
|
|
|
8968
9121
|
}
|
|
8969
9122
|
}
|
|
8970
9123
|
|
|
8971
|
-
const skipPatterns = [
|
|
8972
|
-
/^data-/i,
|
|
8973
|
-
/^aria-/i,
|
|
8974
|
-
/^on[a-z]/i,
|
|
8975
|
-
/^xml(ns)?:/i,
|
|
8976
|
-
/^:/,
|
|
8977
|
-
/^@/,
|
|
8978
|
-
/^ng-/i,
|
|
8979
|
-
/^v-/i,
|
|
8980
|
-
/^x-/i,
|
|
8981
|
-
/^\[/
|
|
8982
|
-
];
|
|
9124
|
+
const skipPatterns = [/^:/, /^@/, /^ng-/i, /^v-/i, /^x-/i, /^\[/, /^#/];
|
|
8983
9125
|
function isKnownDynamicAttr(attr) {
|
|
8984
9126
|
return skipPatterns.some((pattern) => pattern.test(attr));
|
|
8985
9127
|
}
|
|
@@ -9001,6 +9143,9 @@ class NoUnknownAttributes extends Rule {
|
|
|
9001
9143
|
if (attr in meta.attributes) {
|
|
9002
9144
|
return;
|
|
9003
9145
|
}
|
|
9146
|
+
if (isPatternAttribute(attr, meta.patternAttributes)) {
|
|
9147
|
+
return;
|
|
9148
|
+
}
|
|
9004
9149
|
if (isKnownDynamicAttr(attr)) {
|
|
9005
9150
|
return;
|
|
9006
9151
|
}
|
|
@@ -12639,7 +12784,7 @@ class EventHandler {
|
|
|
12639
12784
|
}
|
|
12640
12785
|
|
|
12641
12786
|
const name = "html-validate";
|
|
12642
|
-
const version = "11.
|
|
12787
|
+
const version = "11.4.0";
|
|
12643
12788
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
12644
12789
|
|
|
12645
12790
|
function freeze(src) {
|