html-validate 8.8.0 → 8.9.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/cli.js +36 -21
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core.js +298 -69
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/elements.js +18 -0
- package/dist/cjs/elements.js.map +1 -1
- package/dist/cjs/tsdoc-metadata.json +1 -1
- package/dist/es/cli.js +37 -22
- package/dist/es/cli.js.map +1 -1
- package/dist/es/core.js +298 -69
- package/dist/es/core.js.map +1 -1
- package/dist/es/elements.js +18 -0
- package/dist/es/elements.js.map +1 -1
- package/dist/schema/elements.json +6 -0
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types/browser.d.ts +17 -2
- package/dist/types/index.d.ts +17 -2
- package/package.json +12 -12
package/dist/es/core.js
CHANGED
|
@@ -591,6 +591,18 @@ const patternProperties = {
|
|
|
591
591
|
description: "Script-supporting elements are elements which can be inserted where othersise not permitted to assist in templating",
|
|
592
592
|
type: "boolean"
|
|
593
593
|
},
|
|
594
|
+
focusable: {
|
|
595
|
+
title: "Mark this element as focusable",
|
|
596
|
+
description: "This element may contain an associated label element.",
|
|
597
|
+
anyOf: [
|
|
598
|
+
{
|
|
599
|
+
type: "boolean"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
"function": true
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
},
|
|
594
606
|
form: {
|
|
595
607
|
title: "Mark element as a submittable form element",
|
|
596
608
|
type: "boolean"
|
|
@@ -965,6 +977,7 @@ const MetaCopyableProperty = [
|
|
|
965
977
|
"embedded",
|
|
966
978
|
"interactive",
|
|
967
979
|
"transparent",
|
|
980
|
+
"focusable",
|
|
968
981
|
"form",
|
|
969
982
|
"formAssociated",
|
|
970
983
|
"labelable",
|
|
@@ -1037,6 +1050,7 @@ function migrateElement(src) {
|
|
|
1037
1050
|
},
|
|
1038
1051
|
attributes: migrateAttributes(src),
|
|
1039
1052
|
textContent: src.textContent,
|
|
1053
|
+
focusable: src.focusable ?? false,
|
|
1040
1054
|
implicitRole: src.implicitRole ?? (() => null)
|
|
1041
1055
|
};
|
|
1042
1056
|
delete result.deprecatedAttributes;
|
|
@@ -1292,6 +1306,9 @@ function expandProperties(node, entry) {
|
|
|
1292
1306
|
setMetaProperty(entry, key, evaluateProperty(node, property));
|
|
1293
1307
|
}
|
|
1294
1308
|
}
|
|
1309
|
+
if (typeof entry.focusable === "function") {
|
|
1310
|
+
setMetaProperty(entry, "focusable", entry.focusable(node._adapter));
|
|
1311
|
+
}
|
|
1295
1312
|
}
|
|
1296
1313
|
function expandRegexValue(value) {
|
|
1297
1314
|
if (value instanceof RegExp) {
|
|
@@ -1506,10 +1523,10 @@ class Context {
|
|
|
1506
1523
|
constructor(source) {
|
|
1507
1524
|
this.state = State.INITIAL;
|
|
1508
1525
|
this.string = source.data;
|
|
1509
|
-
this.filename = source.filename
|
|
1510
|
-
this.offset = source.offset
|
|
1511
|
-
this.line = source.line
|
|
1512
|
-
this.column = source.column
|
|
1526
|
+
this.filename = source.filename;
|
|
1527
|
+
this.offset = source.offset;
|
|
1528
|
+
this.line = source.line;
|
|
1529
|
+
this.column = source.column;
|
|
1513
1530
|
this.contentModel = 1 /* TEXT */;
|
|
1514
1531
|
}
|
|
1515
1532
|
getTruncatedLine(n = 13) {
|
|
@@ -1542,6 +1559,16 @@ class Context {
|
|
|
1542
1559
|
}
|
|
1543
1560
|
}
|
|
1544
1561
|
|
|
1562
|
+
function normalizeSource(source) {
|
|
1563
|
+
return {
|
|
1564
|
+
filename: "",
|
|
1565
|
+
offset: 0,
|
|
1566
|
+
line: 1,
|
|
1567
|
+
column: 1,
|
|
1568
|
+
...source
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1545
1572
|
var NodeType = /* @__PURE__ */ ((NodeType2) => {
|
|
1546
1573
|
NodeType2[NodeType2["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
|
|
1547
1574
|
NodeType2[NodeType2["TEXT_NODE"] = 3] = "TEXT_NODE";
|
|
@@ -3135,8 +3162,21 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
|
|
|
3135
3162
|
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
3136
3163
|
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
3137
3164
|
const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
|
|
3165
|
+
const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
|
|
3138
3166
|
function inAccessibilityTree(node) {
|
|
3139
|
-
|
|
3167
|
+
if (isAriaHidden(node)) {
|
|
3168
|
+
return false;
|
|
3169
|
+
}
|
|
3170
|
+
if (isPresentation(node)) {
|
|
3171
|
+
return false;
|
|
3172
|
+
}
|
|
3173
|
+
if (isHTMLHidden(node)) {
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
if (isStyleHidden(node)) {
|
|
3177
|
+
return false;
|
|
3178
|
+
}
|
|
3179
|
+
return true;
|
|
3140
3180
|
}
|
|
3141
3181
|
function isAriaHiddenImpl(node) {
|
|
3142
3182
|
const isHidden = (node2) => {
|
|
@@ -3174,22 +3214,41 @@ function isHTMLHidden(node, details) {
|
|
|
3174
3214
|
const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
|
|
3175
3215
|
return details ? result : result.byParent || result.bySelf;
|
|
3176
3216
|
}
|
|
3217
|
+
function isStyleHiddenImpl(node) {
|
|
3218
|
+
const isHidden = (node2) => {
|
|
3219
|
+
const style = node2.getAttribute("style");
|
|
3220
|
+
const { display, visibility } = parseCssDeclaration(style == null ? void 0 : style.value);
|
|
3221
|
+
return display === "none" || visibility === "hidden";
|
|
3222
|
+
};
|
|
3223
|
+
const byParent = node.parent ? isStyleHidden(node.parent) : false;
|
|
3224
|
+
const bySelf = isHidden(node);
|
|
3225
|
+
return byParent || bySelf;
|
|
3226
|
+
}
|
|
3227
|
+
function isStyleHidden(node) {
|
|
3228
|
+
const cached = node.cacheGet(STYLE_HIDDEN_CACHE);
|
|
3229
|
+
if (cached) {
|
|
3230
|
+
return cached;
|
|
3231
|
+
}
|
|
3232
|
+
return node.cacheSet(STYLE_HIDDEN_CACHE, isStyleHiddenImpl(node));
|
|
3233
|
+
}
|
|
3177
3234
|
function isPresentation(node) {
|
|
3178
3235
|
if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
|
|
3179
3236
|
return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
|
|
3180
3237
|
}
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3238
|
+
const meta = node.meta;
|
|
3239
|
+
if (meta && meta.interactive) {
|
|
3240
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3241
|
+
}
|
|
3242
|
+
const tabindex = node.getAttribute("tabindex");
|
|
3243
|
+
if (tabindex) {
|
|
3244
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3245
|
+
}
|
|
3246
|
+
const role = node.getAttribute("role");
|
|
3247
|
+
if (role && (role.value === "presentation" || role.value === "none")) {
|
|
3248
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, true);
|
|
3249
|
+
} else {
|
|
3250
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3251
|
+
}
|
|
3193
3252
|
}
|
|
3194
3253
|
|
|
3195
3254
|
const cachePrefix = classifyNodeText.name;
|
|
@@ -6177,6 +6236,58 @@ class HeadingLevel extends Rule {
|
|
|
6177
6236
|
}
|
|
6178
6237
|
}
|
|
6179
6238
|
|
|
6239
|
+
class HiddenFocusable extends Rule {
|
|
6240
|
+
documentation(context) {
|
|
6241
|
+
const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
|
|
6242
|
+
return {
|
|
6243
|
+
description: [
|
|
6244
|
+
`\`aria-hidden\` cannot be used on focusable elements.${byParent}`,
|
|
6245
|
+
"",
|
|
6246
|
+
"When focusable elements are hidden with `aria-hidden` they are still reachable using conventional means such as a mouse or keyboard but won't be exposed to assistive technology (AT).",
|
|
6247
|
+
"This is often confusing for users of AT such as screenreaders.",
|
|
6248
|
+
"",
|
|
6249
|
+
"To fix this either:",
|
|
6250
|
+
" - Remove `aria-hidden`.",
|
|
6251
|
+
" - Remove the element from the DOM instead.",
|
|
6252
|
+
" - Use the `hidden` attribute or similar means to hide the element."
|
|
6253
|
+
].join("\n"),
|
|
6254
|
+
url: "https://html-validate.org/rules/hidden-focusable.html"
|
|
6255
|
+
};
|
|
6256
|
+
}
|
|
6257
|
+
setup() {
|
|
6258
|
+
const focusable = this.getTagsWithProperty("focusable");
|
|
6259
|
+
const selector = ["[tabindex]", ...focusable].join(",");
|
|
6260
|
+
this.on("dom:ready", (event) => {
|
|
6261
|
+
const { document } = event;
|
|
6262
|
+
for (const element of document.querySelectorAll(selector)) {
|
|
6263
|
+
if (isHTMLHidden(element) || isStyleHidden(element)) {
|
|
6264
|
+
continue;
|
|
6265
|
+
}
|
|
6266
|
+
if (isAriaHidden(element)) {
|
|
6267
|
+
this.validateElement(element);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
});
|
|
6271
|
+
}
|
|
6272
|
+
validateElement(element) {
|
|
6273
|
+
const { meta } = element;
|
|
6274
|
+
const tabindex = element.getAttribute("tabindex");
|
|
6275
|
+
if (meta && !meta.focusable && !tabindex) {
|
|
6276
|
+
return;
|
|
6277
|
+
}
|
|
6278
|
+
const attribute = element.getAttribute("aria-hidden");
|
|
6279
|
+
const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
|
|
6280
|
+
const location = attribute ? attribute.keyLocation : element.location;
|
|
6281
|
+
const context = attribute ? "self" : "parent";
|
|
6282
|
+
this.report({
|
|
6283
|
+
node: element,
|
|
6284
|
+
message,
|
|
6285
|
+
location,
|
|
6286
|
+
context
|
|
6287
|
+
});
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6180
6291
|
const defaults$g = {
|
|
6181
6292
|
pattern: "kebabcase"
|
|
6182
6293
|
};
|
|
@@ -6362,7 +6473,7 @@ function isHidden(node, context) {
|
|
|
6362
6473
|
if (reference && reference.isSameNode(node)) {
|
|
6363
6474
|
return false;
|
|
6364
6475
|
} else {
|
|
6365
|
-
return
|
|
6476
|
+
return !inAccessibilityTree(node);
|
|
6366
6477
|
}
|
|
6367
6478
|
}
|
|
6368
6479
|
function hasImgAltText(node, context) {
|
|
@@ -7939,7 +8050,7 @@ class RequireSri extends Rule {
|
|
|
7939
8050
|
}
|
|
7940
8051
|
documentation() {
|
|
7941
8052
|
return {
|
|
7942
|
-
description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent manipulation from Content Delivery Networks
|
|
8053
|
+
description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent tampering or manipulation from Content Delivery Networks (CDN), rouge proxies, malicious entities, etc.`,
|
|
7943
8054
|
url: "https://html-validate.org/rules/require-sri.html"
|
|
7944
8055
|
};
|
|
7945
8056
|
}
|
|
@@ -8352,6 +8463,107 @@ class TextContent extends Rule {
|
|
|
8352
8463
|
}
|
|
8353
8464
|
}
|
|
8354
8465
|
|
|
8466
|
+
function getTextFromReference(document, id) {
|
|
8467
|
+
if (!id || id instanceof DynamicValue) {
|
|
8468
|
+
return id;
|
|
8469
|
+
}
|
|
8470
|
+
const selector = `#${id}`;
|
|
8471
|
+
const ref = document.querySelector(selector);
|
|
8472
|
+
if (ref) {
|
|
8473
|
+
return ref.textContent;
|
|
8474
|
+
} else {
|
|
8475
|
+
return selector;
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
function getTextEntryFromElement(document, node) {
|
|
8479
|
+
const ariaLabel = node.getAttribute("aria-label");
|
|
8480
|
+
if (ariaLabel) {
|
|
8481
|
+
return {
|
|
8482
|
+
node,
|
|
8483
|
+
text: ariaLabel.value,
|
|
8484
|
+
location: ariaLabel.keyLocation
|
|
8485
|
+
};
|
|
8486
|
+
}
|
|
8487
|
+
const ariaLabelledby = node.getAttribute("aria-labelledby");
|
|
8488
|
+
if (ariaLabelledby) {
|
|
8489
|
+
const text = getTextFromReference(document, ariaLabelledby.value);
|
|
8490
|
+
return {
|
|
8491
|
+
node,
|
|
8492
|
+
text,
|
|
8493
|
+
location: ariaLabelledby.keyLocation
|
|
8494
|
+
};
|
|
8495
|
+
}
|
|
8496
|
+
return {
|
|
8497
|
+
node,
|
|
8498
|
+
text: null,
|
|
8499
|
+
location: node.location
|
|
8500
|
+
};
|
|
8501
|
+
}
|
|
8502
|
+
class UniqueLandmark extends Rule {
|
|
8503
|
+
documentation() {
|
|
8504
|
+
return {
|
|
8505
|
+
description: [
|
|
8506
|
+
"When the same type of landmark is present more than once in the same document each must be uniquely identifiable with a non-empty and unique name.",
|
|
8507
|
+
"For instance, if the document has two `<nav>` elements each of them need an accessible name to be distinguished from each other.",
|
|
8508
|
+
"",
|
|
8509
|
+
"The following elements / roles are considered landmarks:",
|
|
8510
|
+
"",
|
|
8511
|
+
' - `aside` or `[role="complementary"]`',
|
|
8512
|
+
' - `footer` or `[role="contentinfo"]`',
|
|
8513
|
+
' - `form` or `[role="form"]`',
|
|
8514
|
+
' - `header` or `[role="banner"]`',
|
|
8515
|
+
' - `main` or `[role="main"]`',
|
|
8516
|
+
' - `nav` or `[role="navigation"]`',
|
|
8517
|
+
' - `section` or `[role="region"]`',
|
|
8518
|
+
"",
|
|
8519
|
+
"To fix this either:",
|
|
8520
|
+
"",
|
|
8521
|
+
" - Add `aria-label`.",
|
|
8522
|
+
" - Add `aria-labelledby`.",
|
|
8523
|
+
" - Remove one of the landmarks."
|
|
8524
|
+
].join("\n"),
|
|
8525
|
+
url: "https://html-validate.org/rules/unique-landmark.html"
|
|
8526
|
+
};
|
|
8527
|
+
}
|
|
8528
|
+
setup() {
|
|
8529
|
+
const selectors = [
|
|
8530
|
+
'aside, [role="complementary"]',
|
|
8531
|
+
'footer, [role="contentinfo"]',
|
|
8532
|
+
'form, [role="form"]',
|
|
8533
|
+
'header, [role="banner"]',
|
|
8534
|
+
'main, [role="main"]',
|
|
8535
|
+
'nav, [role="navigation"]',
|
|
8536
|
+
'section, [role="region"]'
|
|
8537
|
+
/* <search> does not (yet?) require a unique name */
|
|
8538
|
+
];
|
|
8539
|
+
this.on("dom:ready", (event) => {
|
|
8540
|
+
const { document } = event;
|
|
8541
|
+
for (const selector of selectors) {
|
|
8542
|
+
const nodes = document.querySelectorAll(selector);
|
|
8543
|
+
if (nodes.length <= 1) {
|
|
8544
|
+
continue;
|
|
8545
|
+
}
|
|
8546
|
+
const entries = nodes.map((it) => getTextEntryFromElement(document, it));
|
|
8547
|
+
for (const entry of entries) {
|
|
8548
|
+
if (entry.text instanceof DynamicValue) {
|
|
8549
|
+
continue;
|
|
8550
|
+
}
|
|
8551
|
+
const dup = entries.filter((it) => it.text === entry.text).length > 1;
|
|
8552
|
+
if (!entry.text || dup) {
|
|
8553
|
+
const message = `Landmarks must have a non-empty and unique accessible name (aria-label or aria-labelledby)`;
|
|
8554
|
+
const location = entry.location;
|
|
8555
|
+
this.report({
|
|
8556
|
+
node: entry.node,
|
|
8557
|
+
message,
|
|
8558
|
+
location
|
|
8559
|
+
});
|
|
8560
|
+
}
|
|
8561
|
+
}
|
|
8562
|
+
}
|
|
8563
|
+
});
|
|
8564
|
+
}
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8355
8567
|
const defaults$4 = {
|
|
8356
8568
|
ignoreCase: false,
|
|
8357
8569
|
requireSemicolon: true
|
|
@@ -8741,7 +8953,7 @@ class H32 extends Rule {
|
|
|
8741
8953
|
}
|
|
8742
8954
|
function isSubmit(node) {
|
|
8743
8955
|
const type = node.getAttribute("type");
|
|
8744
|
-
return Boolean(type
|
|
8956
|
+
return Boolean(!type || type.valueMatches(/submit|image/));
|
|
8745
8957
|
}
|
|
8746
8958
|
function isAssociated(id, node) {
|
|
8747
8959
|
const form = node.getAttribute("form");
|
|
@@ -8833,29 +9045,35 @@ class H37 extends Rule {
|
|
|
8833
9045
|
};
|
|
8834
9046
|
}
|
|
8835
9047
|
setup() {
|
|
8836
|
-
this.on("
|
|
8837
|
-
const
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
if (!inAccessibilityTree(node)) {
|
|
8842
|
-
return;
|
|
9048
|
+
this.on("dom:ready", (event) => {
|
|
9049
|
+
const { document } = event;
|
|
9050
|
+
const nodes = document.querySelectorAll("img, input");
|
|
9051
|
+
for (const node of nodes) {
|
|
9052
|
+
this.validateNode(node);
|
|
8843
9053
|
}
|
|
8844
|
-
|
|
9054
|
+
});
|
|
9055
|
+
}
|
|
9056
|
+
validateNode(node) {
|
|
9057
|
+
if (!needsAlt(node)) {
|
|
9058
|
+
return;
|
|
9059
|
+
}
|
|
9060
|
+
if (!inAccessibilityTree(node)) {
|
|
9061
|
+
return;
|
|
9062
|
+
}
|
|
9063
|
+
if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
|
|
9064
|
+
return;
|
|
9065
|
+
}
|
|
9066
|
+
for (const attr of this.options.alias) {
|
|
9067
|
+
if (node.getAttribute(attr)) {
|
|
8845
9068
|
return;
|
|
8846
9069
|
}
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
|
|
8855
|
-
} else {
|
|
8856
|
-
this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
|
|
8857
|
-
}
|
|
8858
|
-
});
|
|
9070
|
+
}
|
|
9071
|
+
if (node.hasAttribute("alt")) {
|
|
9072
|
+
const attr = node.getAttribute("alt");
|
|
9073
|
+
this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
|
|
9074
|
+
} else {
|
|
9075
|
+
this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
|
|
9076
|
+
}
|
|
8859
9077
|
}
|
|
8860
9078
|
}
|
|
8861
9079
|
|
|
@@ -8946,7 +9164,7 @@ class H71 extends Rule {
|
|
|
8946
9164
|
}
|
|
8947
9165
|
}
|
|
8948
9166
|
|
|
8949
|
-
const bundledRules$
|
|
9167
|
+
const bundledRules$1 = {
|
|
8950
9168
|
"wcag/h30": H30,
|
|
8951
9169
|
"wcag/h32": H32,
|
|
8952
9170
|
"wcag/h36": H36,
|
|
@@ -8955,7 +9173,7 @@ const bundledRules$2 = {
|
|
|
8955
9173
|
"wcag/h67": H67,
|
|
8956
9174
|
"wcag/h71": H71
|
|
8957
9175
|
};
|
|
8958
|
-
var WCAG = bundledRules$
|
|
9176
|
+
var WCAG = bundledRules$1;
|
|
8959
9177
|
|
|
8960
9178
|
const bundledRules = {
|
|
8961
9179
|
"allowed-links": AllowedLinks,
|
|
@@ -8991,6 +9209,7 @@ const bundledRules = {
|
|
|
8991
9209
|
"empty-title": EmptyTitle,
|
|
8992
9210
|
"form-dup-name": FormDupName,
|
|
8993
9211
|
"heading-level": HeadingLevel,
|
|
9212
|
+
"hidden-focusable": HiddenFocusable,
|
|
8994
9213
|
"id-pattern": IdPattern,
|
|
8995
9214
|
"input-attributes": InputAttributes,
|
|
8996
9215
|
"input-missing-label": InputMissingLabel,
|
|
@@ -9031,13 +9250,13 @@ const bundledRules = {
|
|
|
9031
9250
|
"svg-focusable": SvgFocusable,
|
|
9032
9251
|
"tel-non-breaking": TelNonBreaking,
|
|
9033
9252
|
"text-content": TextContent,
|
|
9253
|
+
"unique-landmark": UniqueLandmark,
|
|
9034
9254
|
"unrecognized-char-ref": UnknownCharReference,
|
|
9035
9255
|
"valid-id": ValidID,
|
|
9036
9256
|
"void-content": VoidContent,
|
|
9037
9257
|
"void-style": VoidStyle,
|
|
9038
9258
|
...WCAG
|
|
9039
9259
|
};
|
|
9040
|
-
var bundledRules$1 = bundledRules;
|
|
9041
9260
|
|
|
9042
9261
|
var defaultConfig = {};
|
|
9043
9262
|
|
|
@@ -9049,6 +9268,7 @@ const config$4 = {
|
|
|
9049
9268
|
"deprecated-rule": "warn",
|
|
9050
9269
|
"empty-heading": "error",
|
|
9051
9270
|
"empty-title": "error",
|
|
9271
|
+
"hidden-focusable": "error",
|
|
9052
9272
|
"meta-refresh": "error",
|
|
9053
9273
|
"multiple-labeled-controls": "error",
|
|
9054
9274
|
"no-autoplay": ["error", { include: ["audio", "video"] }],
|
|
@@ -9060,6 +9280,7 @@ const config$4 = {
|
|
|
9060
9280
|
"prefer-native-element": "error",
|
|
9061
9281
|
"svg-focusable": "off",
|
|
9062
9282
|
"text-content": "error",
|
|
9283
|
+
"unique-landmark": "error",
|
|
9063
9284
|
"wcag/h30": "error",
|
|
9064
9285
|
"wcag/h32": "error",
|
|
9065
9286
|
"wcag/h36": "error",
|
|
@@ -9122,6 +9343,7 @@ const config$1 = {
|
|
|
9122
9343
|
"empty-heading": "error",
|
|
9123
9344
|
"empty-title": "error",
|
|
9124
9345
|
"form-dup-name": "error",
|
|
9346
|
+
"hidden-focusable": "error",
|
|
9125
9347
|
"input-attributes": "error",
|
|
9126
9348
|
"long-title": "error",
|
|
9127
9349
|
"map-dup-name": "error",
|
|
@@ -9154,6 +9376,7 @@ const config$1 = {
|
|
|
9154
9376
|
"svg-focusable": "off",
|
|
9155
9377
|
"tel-non-breaking": "error",
|
|
9156
9378
|
"text-content": "error",
|
|
9379
|
+
"unique-landmark": "error",
|
|
9157
9380
|
"unrecognized-char-ref": "error",
|
|
9158
9381
|
"valid-id": ["error", { relaxed: false }],
|
|
9159
9382
|
void: "off",
|
|
@@ -9498,7 +9721,7 @@ class Config {
|
|
|
9498
9721
|
if (configData.rules) {
|
|
9499
9722
|
const normalizedRules = Config.getRulesObject(configData.rules);
|
|
9500
9723
|
for (const [ruleId, [, ruleOptions]] of normalizedRules.entries()) {
|
|
9501
|
-
const cls = bundledRules
|
|
9724
|
+
const cls = bundledRules[ruleId];
|
|
9502
9725
|
const path = `/rules/${ruleId}/1`;
|
|
9503
9726
|
Rule.validateOptions(cls, ruleId, path, ruleOptions, filename, configData);
|
|
9504
9727
|
}
|
|
@@ -9871,16 +10094,17 @@ class EventHandler {
|
|
|
9871
10094
|
* @returns Unregistration function.
|
|
9872
10095
|
*/
|
|
9873
10096
|
on(event, callback) {
|
|
9874
|
-
const
|
|
10097
|
+
const { listeners } = this;
|
|
10098
|
+
const names = event.split(",").map((it) => it.trim());
|
|
9875
10099
|
for (const name of names) {
|
|
9876
|
-
|
|
9877
|
-
|
|
10100
|
+
const list = listeners[name] ?? [];
|
|
10101
|
+
listeners[name] = list;
|
|
10102
|
+
list.push(callback);
|
|
9878
10103
|
}
|
|
9879
10104
|
return () => {
|
|
9880
10105
|
for (const name of names) {
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
});
|
|
10106
|
+
const list = listeners[name];
|
|
10107
|
+
this.listeners[name] = list.filter((fn) => fn !== callback);
|
|
9884
10108
|
}
|
|
9885
10109
|
};
|
|
9886
10110
|
}
|
|
@@ -9906,10 +10130,15 @@ class EventHandler {
|
|
|
9906
10130
|
* @param data - Event data.
|
|
9907
10131
|
*/
|
|
9908
10132
|
trigger(event, data) {
|
|
9909
|
-
const
|
|
9910
|
-
callbacks.forEach((listener) => {
|
|
10133
|
+
for (const listener of this.getCallbacks(event)) {
|
|
9911
10134
|
listener.call(null, event, data);
|
|
9912
|
-
}
|
|
10135
|
+
}
|
|
10136
|
+
}
|
|
10137
|
+
getCallbacks(event) {
|
|
10138
|
+
const { listeners } = this;
|
|
10139
|
+
const callbacks = listeners[event] ?? [];
|
|
10140
|
+
const global = listeners["*"] ?? [];
|
|
10141
|
+
return [...callbacks, ...global];
|
|
9913
10142
|
}
|
|
9914
10143
|
}
|
|
9915
10144
|
|
|
@@ -9979,10 +10208,10 @@ class Parser {
|
|
|
9979
10208
|
location: null
|
|
9980
10209
|
});
|
|
9981
10210
|
this.dom = new DOMTree({
|
|
9982
|
-
filename: source.filename
|
|
9983
|
-
offset: source.offset
|
|
9984
|
-
line: source.line
|
|
9985
|
-
column: source.column
|
|
10211
|
+
filename: source.filename,
|
|
10212
|
+
offset: source.offset,
|
|
10213
|
+
line: source.line,
|
|
10214
|
+
column: source.column,
|
|
9986
10215
|
size: 0
|
|
9987
10216
|
});
|
|
9988
10217
|
this.trigger("dom:load", {
|
|
@@ -10506,7 +10735,7 @@ function isThenable(value) {
|
|
|
10506
10735
|
return value && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
10507
10736
|
}
|
|
10508
10737
|
|
|
10509
|
-
const ruleIds = new Set(Object.keys(bundledRules
|
|
10738
|
+
const ruleIds = new Set(Object.keys(bundledRules));
|
|
10510
10739
|
function ruleExists(ruleId) {
|
|
10511
10740
|
return ruleIds.has(ruleId);
|
|
10512
10741
|
}
|
|
@@ -10595,9 +10824,7 @@ class Reporter {
|
|
|
10595
10824
|
valid: this.isValid(),
|
|
10596
10825
|
results: Object.keys(this.result).map((filePath) => {
|
|
10597
10826
|
const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
|
|
10598
|
-
const source = (sources ?? []).find(
|
|
10599
|
-
(source2) => filePath === (source2.filename ?? "")
|
|
10600
|
-
);
|
|
10827
|
+
const source = (sources ?? []).find((source2) => filePath === source2.filename);
|
|
10601
10828
|
return {
|
|
10602
10829
|
filePath,
|
|
10603
10830
|
messages,
|
|
@@ -10665,7 +10892,7 @@ class Engine {
|
|
|
10665
10892
|
this.ParserClass = ParserClass;
|
|
10666
10893
|
const result = this.initPlugins(this.config);
|
|
10667
10894
|
this.availableRules = {
|
|
10668
|
-
...bundledRules
|
|
10895
|
+
...bundledRules,
|
|
10669
10896
|
...result.availableRules
|
|
10670
10897
|
};
|
|
10671
10898
|
}
|
|
@@ -11094,10 +11321,11 @@ class HtmlValidate {
|
|
|
11094
11321
|
* @returns Report output.
|
|
11095
11322
|
*/
|
|
11096
11323
|
async validateSource(input, configOverride) {
|
|
11097
|
-
const
|
|
11098
|
-
const
|
|
11324
|
+
const source = normalizeSource(input);
|
|
11325
|
+
const config = await this.getConfigFor(source.filename, configOverride);
|
|
11326
|
+
const transformedSource = config.transformSource(source);
|
|
11099
11327
|
const engine = new Engine(config, Parser);
|
|
11100
|
-
return engine.lint(
|
|
11328
|
+
return engine.lint(transformedSource);
|
|
11101
11329
|
}
|
|
11102
11330
|
/**
|
|
11103
11331
|
* Parse and validate HTML from [[Source]].
|
|
@@ -11107,10 +11335,11 @@ class HtmlValidate {
|
|
|
11107
11335
|
* @returns Report output.
|
|
11108
11336
|
*/
|
|
11109
11337
|
validateSourceSync(input, configOverride) {
|
|
11110
|
-
const
|
|
11111
|
-
const
|
|
11338
|
+
const source = normalizeSource(input);
|
|
11339
|
+
const config = this.getConfigForSync(source.filename, configOverride);
|
|
11340
|
+
const transformedSource = config.transformSource(source);
|
|
11112
11341
|
const engine = new Engine(config, Parser);
|
|
11113
|
-
return engine.lint(
|
|
11342
|
+
return engine.lint(transformedSource);
|
|
11114
11343
|
}
|
|
11115
11344
|
/**
|
|
11116
11345
|
* Parse and validate HTML from file.
|
|
@@ -11419,7 +11648,7 @@ class HtmlValidate {
|
|
|
11419
11648
|
}
|
|
11420
11649
|
|
|
11421
11650
|
const name = "html-validate";
|
|
11422
|
-
const version = "8.
|
|
11651
|
+
const version = "8.9.0";
|
|
11423
11652
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
11424
11653
|
|
|
11425
11654
|
function definePlugin(plugin) {
|