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/cjs/core.js
CHANGED
|
@@ -601,6 +601,18 @@ const patternProperties = {
|
|
|
601
601
|
description: "Script-supporting elements are elements which can be inserted where othersise not permitted to assist in templating",
|
|
602
602
|
type: "boolean"
|
|
603
603
|
},
|
|
604
|
+
focusable: {
|
|
605
|
+
title: "Mark this element as focusable",
|
|
606
|
+
description: "This element may contain an associated label element.",
|
|
607
|
+
anyOf: [
|
|
608
|
+
{
|
|
609
|
+
type: "boolean"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
"function": true
|
|
613
|
+
}
|
|
614
|
+
]
|
|
615
|
+
},
|
|
604
616
|
form: {
|
|
605
617
|
title: "Mark element as a submittable form element",
|
|
606
618
|
type: "boolean"
|
|
@@ -975,6 +987,7 @@ const MetaCopyableProperty = [
|
|
|
975
987
|
"embedded",
|
|
976
988
|
"interactive",
|
|
977
989
|
"transparent",
|
|
990
|
+
"focusable",
|
|
978
991
|
"form",
|
|
979
992
|
"formAssociated",
|
|
980
993
|
"labelable",
|
|
@@ -1047,6 +1060,7 @@ function migrateElement(src) {
|
|
|
1047
1060
|
},
|
|
1048
1061
|
attributes: migrateAttributes(src),
|
|
1049
1062
|
textContent: src.textContent,
|
|
1063
|
+
focusable: src.focusable ?? false,
|
|
1050
1064
|
implicitRole: src.implicitRole ?? (() => null)
|
|
1051
1065
|
};
|
|
1052
1066
|
delete result.deprecatedAttributes;
|
|
@@ -1302,6 +1316,9 @@ function expandProperties(node, entry) {
|
|
|
1302
1316
|
setMetaProperty(entry, key, evaluateProperty(node, property));
|
|
1303
1317
|
}
|
|
1304
1318
|
}
|
|
1319
|
+
if (typeof entry.focusable === "function") {
|
|
1320
|
+
setMetaProperty(entry, "focusable", entry.focusable(node._adapter));
|
|
1321
|
+
}
|
|
1305
1322
|
}
|
|
1306
1323
|
function expandRegexValue(value) {
|
|
1307
1324
|
if (value instanceof RegExp) {
|
|
@@ -1516,10 +1533,10 @@ class Context {
|
|
|
1516
1533
|
constructor(source) {
|
|
1517
1534
|
this.state = State.INITIAL;
|
|
1518
1535
|
this.string = source.data;
|
|
1519
|
-
this.filename = source.filename
|
|
1520
|
-
this.offset = source.offset
|
|
1521
|
-
this.line = source.line
|
|
1522
|
-
this.column = source.column
|
|
1536
|
+
this.filename = source.filename;
|
|
1537
|
+
this.offset = source.offset;
|
|
1538
|
+
this.line = source.line;
|
|
1539
|
+
this.column = source.column;
|
|
1523
1540
|
this.contentModel = 1 /* TEXT */;
|
|
1524
1541
|
}
|
|
1525
1542
|
getTruncatedLine(n = 13) {
|
|
@@ -1552,6 +1569,16 @@ class Context {
|
|
|
1552
1569
|
}
|
|
1553
1570
|
}
|
|
1554
1571
|
|
|
1572
|
+
function normalizeSource(source) {
|
|
1573
|
+
return {
|
|
1574
|
+
filename: "",
|
|
1575
|
+
offset: 0,
|
|
1576
|
+
line: 1,
|
|
1577
|
+
column: 1,
|
|
1578
|
+
...source
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1555
1582
|
var NodeType = /* @__PURE__ */ ((NodeType2) => {
|
|
1556
1583
|
NodeType2[NodeType2["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
|
|
1557
1584
|
NodeType2[NodeType2["TEXT_NODE"] = 3] = "TEXT_NODE";
|
|
@@ -3145,8 +3172,21 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
|
|
|
3145
3172
|
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
3146
3173
|
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
3147
3174
|
const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
|
|
3175
|
+
const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
|
|
3148
3176
|
function inAccessibilityTree(node) {
|
|
3149
|
-
|
|
3177
|
+
if (isAriaHidden(node)) {
|
|
3178
|
+
return false;
|
|
3179
|
+
}
|
|
3180
|
+
if (isPresentation(node)) {
|
|
3181
|
+
return false;
|
|
3182
|
+
}
|
|
3183
|
+
if (isHTMLHidden(node)) {
|
|
3184
|
+
return false;
|
|
3185
|
+
}
|
|
3186
|
+
if (isStyleHidden(node)) {
|
|
3187
|
+
return false;
|
|
3188
|
+
}
|
|
3189
|
+
return true;
|
|
3150
3190
|
}
|
|
3151
3191
|
function isAriaHiddenImpl(node) {
|
|
3152
3192
|
const isHidden = (node2) => {
|
|
@@ -3184,22 +3224,41 @@ function isHTMLHidden(node, details) {
|
|
|
3184
3224
|
const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
|
|
3185
3225
|
return details ? result : result.byParent || result.bySelf;
|
|
3186
3226
|
}
|
|
3227
|
+
function isStyleHiddenImpl(node) {
|
|
3228
|
+
const isHidden = (node2) => {
|
|
3229
|
+
const style = node2.getAttribute("style");
|
|
3230
|
+
const { display, visibility } = parseCssDeclaration(style == null ? void 0 : style.value);
|
|
3231
|
+
return display === "none" || visibility === "hidden";
|
|
3232
|
+
};
|
|
3233
|
+
const byParent = node.parent ? isStyleHidden(node.parent) : false;
|
|
3234
|
+
const bySelf = isHidden(node);
|
|
3235
|
+
return byParent || bySelf;
|
|
3236
|
+
}
|
|
3237
|
+
function isStyleHidden(node) {
|
|
3238
|
+
const cached = node.cacheGet(STYLE_HIDDEN_CACHE);
|
|
3239
|
+
if (cached) {
|
|
3240
|
+
return cached;
|
|
3241
|
+
}
|
|
3242
|
+
return node.cacheSet(STYLE_HIDDEN_CACHE, isStyleHiddenImpl(node));
|
|
3243
|
+
}
|
|
3187
3244
|
function isPresentation(node) {
|
|
3188
3245
|
if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
|
|
3189
3246
|
return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
|
|
3190
3247
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3248
|
+
const meta = node.meta;
|
|
3249
|
+
if (meta && meta.interactive) {
|
|
3250
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3251
|
+
}
|
|
3252
|
+
const tabindex = node.getAttribute("tabindex");
|
|
3253
|
+
if (tabindex) {
|
|
3254
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3255
|
+
}
|
|
3256
|
+
const role = node.getAttribute("role");
|
|
3257
|
+
if (role && (role.value === "presentation" || role.value === "none")) {
|
|
3258
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, true);
|
|
3259
|
+
} else {
|
|
3260
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3261
|
+
}
|
|
3203
3262
|
}
|
|
3204
3263
|
|
|
3205
3264
|
const cachePrefix = classifyNodeText.name;
|
|
@@ -6187,6 +6246,58 @@ class HeadingLevel extends Rule {
|
|
|
6187
6246
|
}
|
|
6188
6247
|
}
|
|
6189
6248
|
|
|
6249
|
+
class HiddenFocusable extends Rule {
|
|
6250
|
+
documentation(context) {
|
|
6251
|
+
const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
|
|
6252
|
+
return {
|
|
6253
|
+
description: [
|
|
6254
|
+
`\`aria-hidden\` cannot be used on focusable elements.${byParent}`,
|
|
6255
|
+
"",
|
|
6256
|
+
"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).",
|
|
6257
|
+
"This is often confusing for users of AT such as screenreaders.",
|
|
6258
|
+
"",
|
|
6259
|
+
"To fix this either:",
|
|
6260
|
+
" - Remove `aria-hidden`.",
|
|
6261
|
+
" - Remove the element from the DOM instead.",
|
|
6262
|
+
" - Use the `hidden` attribute or similar means to hide the element."
|
|
6263
|
+
].join("\n"),
|
|
6264
|
+
url: "https://html-validate.org/rules/hidden-focusable.html"
|
|
6265
|
+
};
|
|
6266
|
+
}
|
|
6267
|
+
setup() {
|
|
6268
|
+
const focusable = this.getTagsWithProperty("focusable");
|
|
6269
|
+
const selector = ["[tabindex]", ...focusable].join(",");
|
|
6270
|
+
this.on("dom:ready", (event) => {
|
|
6271
|
+
const { document } = event;
|
|
6272
|
+
for (const element of document.querySelectorAll(selector)) {
|
|
6273
|
+
if (isHTMLHidden(element) || isStyleHidden(element)) {
|
|
6274
|
+
continue;
|
|
6275
|
+
}
|
|
6276
|
+
if (isAriaHidden(element)) {
|
|
6277
|
+
this.validateElement(element);
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
});
|
|
6281
|
+
}
|
|
6282
|
+
validateElement(element) {
|
|
6283
|
+
const { meta } = element;
|
|
6284
|
+
const tabindex = element.getAttribute("tabindex");
|
|
6285
|
+
if (meta && !meta.focusable && !tabindex) {
|
|
6286
|
+
return;
|
|
6287
|
+
}
|
|
6288
|
+
const attribute = element.getAttribute("aria-hidden");
|
|
6289
|
+
const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
|
|
6290
|
+
const location = attribute ? attribute.keyLocation : element.location;
|
|
6291
|
+
const context = attribute ? "self" : "parent";
|
|
6292
|
+
this.report({
|
|
6293
|
+
node: element,
|
|
6294
|
+
message,
|
|
6295
|
+
location,
|
|
6296
|
+
context
|
|
6297
|
+
});
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
|
|
6190
6301
|
const defaults$g = {
|
|
6191
6302
|
pattern: "kebabcase"
|
|
6192
6303
|
};
|
|
@@ -6372,7 +6483,7 @@ function isHidden(node, context) {
|
|
|
6372
6483
|
if (reference && reference.isSameNode(node)) {
|
|
6373
6484
|
return false;
|
|
6374
6485
|
} else {
|
|
6375
|
-
return
|
|
6486
|
+
return !inAccessibilityTree(node);
|
|
6376
6487
|
}
|
|
6377
6488
|
}
|
|
6378
6489
|
function hasImgAltText(node, context) {
|
|
@@ -7949,7 +8060,7 @@ class RequireSri extends Rule {
|
|
|
7949
8060
|
}
|
|
7950
8061
|
documentation() {
|
|
7951
8062
|
return {
|
|
7952
|
-
description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent manipulation from Content Delivery Networks
|
|
8063
|
+
description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent tampering or manipulation from Content Delivery Networks (CDN), rouge proxies, malicious entities, etc.`,
|
|
7953
8064
|
url: "https://html-validate.org/rules/require-sri.html"
|
|
7954
8065
|
};
|
|
7955
8066
|
}
|
|
@@ -8362,6 +8473,107 @@ class TextContent extends Rule {
|
|
|
8362
8473
|
}
|
|
8363
8474
|
}
|
|
8364
8475
|
|
|
8476
|
+
function getTextFromReference(document, id) {
|
|
8477
|
+
if (!id || id instanceof DynamicValue) {
|
|
8478
|
+
return id;
|
|
8479
|
+
}
|
|
8480
|
+
const selector = `#${id}`;
|
|
8481
|
+
const ref = document.querySelector(selector);
|
|
8482
|
+
if (ref) {
|
|
8483
|
+
return ref.textContent;
|
|
8484
|
+
} else {
|
|
8485
|
+
return selector;
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
function getTextEntryFromElement(document, node) {
|
|
8489
|
+
const ariaLabel = node.getAttribute("aria-label");
|
|
8490
|
+
if (ariaLabel) {
|
|
8491
|
+
return {
|
|
8492
|
+
node,
|
|
8493
|
+
text: ariaLabel.value,
|
|
8494
|
+
location: ariaLabel.keyLocation
|
|
8495
|
+
};
|
|
8496
|
+
}
|
|
8497
|
+
const ariaLabelledby = node.getAttribute("aria-labelledby");
|
|
8498
|
+
if (ariaLabelledby) {
|
|
8499
|
+
const text = getTextFromReference(document, ariaLabelledby.value);
|
|
8500
|
+
return {
|
|
8501
|
+
node,
|
|
8502
|
+
text,
|
|
8503
|
+
location: ariaLabelledby.keyLocation
|
|
8504
|
+
};
|
|
8505
|
+
}
|
|
8506
|
+
return {
|
|
8507
|
+
node,
|
|
8508
|
+
text: null,
|
|
8509
|
+
location: node.location
|
|
8510
|
+
};
|
|
8511
|
+
}
|
|
8512
|
+
class UniqueLandmark extends Rule {
|
|
8513
|
+
documentation() {
|
|
8514
|
+
return {
|
|
8515
|
+
description: [
|
|
8516
|
+
"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.",
|
|
8517
|
+
"For instance, if the document has two `<nav>` elements each of them need an accessible name to be distinguished from each other.",
|
|
8518
|
+
"",
|
|
8519
|
+
"The following elements / roles are considered landmarks:",
|
|
8520
|
+
"",
|
|
8521
|
+
' - `aside` or `[role="complementary"]`',
|
|
8522
|
+
' - `footer` or `[role="contentinfo"]`',
|
|
8523
|
+
' - `form` or `[role="form"]`',
|
|
8524
|
+
' - `header` or `[role="banner"]`',
|
|
8525
|
+
' - `main` or `[role="main"]`',
|
|
8526
|
+
' - `nav` or `[role="navigation"]`',
|
|
8527
|
+
' - `section` or `[role="region"]`',
|
|
8528
|
+
"",
|
|
8529
|
+
"To fix this either:",
|
|
8530
|
+
"",
|
|
8531
|
+
" - Add `aria-label`.",
|
|
8532
|
+
" - Add `aria-labelledby`.",
|
|
8533
|
+
" - Remove one of the landmarks."
|
|
8534
|
+
].join("\n"),
|
|
8535
|
+
url: "https://html-validate.org/rules/unique-landmark.html"
|
|
8536
|
+
};
|
|
8537
|
+
}
|
|
8538
|
+
setup() {
|
|
8539
|
+
const selectors = [
|
|
8540
|
+
'aside, [role="complementary"]',
|
|
8541
|
+
'footer, [role="contentinfo"]',
|
|
8542
|
+
'form, [role="form"]',
|
|
8543
|
+
'header, [role="banner"]',
|
|
8544
|
+
'main, [role="main"]',
|
|
8545
|
+
'nav, [role="navigation"]',
|
|
8546
|
+
'section, [role="region"]'
|
|
8547
|
+
/* <search> does not (yet?) require a unique name */
|
|
8548
|
+
];
|
|
8549
|
+
this.on("dom:ready", (event) => {
|
|
8550
|
+
const { document } = event;
|
|
8551
|
+
for (const selector of selectors) {
|
|
8552
|
+
const nodes = document.querySelectorAll(selector);
|
|
8553
|
+
if (nodes.length <= 1) {
|
|
8554
|
+
continue;
|
|
8555
|
+
}
|
|
8556
|
+
const entries = nodes.map((it) => getTextEntryFromElement(document, it));
|
|
8557
|
+
for (const entry of entries) {
|
|
8558
|
+
if (entry.text instanceof DynamicValue) {
|
|
8559
|
+
continue;
|
|
8560
|
+
}
|
|
8561
|
+
const dup = entries.filter((it) => it.text === entry.text).length > 1;
|
|
8562
|
+
if (!entry.text || dup) {
|
|
8563
|
+
const message = `Landmarks must have a non-empty and unique accessible name (aria-label or aria-labelledby)`;
|
|
8564
|
+
const location = entry.location;
|
|
8565
|
+
this.report({
|
|
8566
|
+
node: entry.node,
|
|
8567
|
+
message,
|
|
8568
|
+
location
|
|
8569
|
+
});
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
8573
|
+
});
|
|
8574
|
+
}
|
|
8575
|
+
}
|
|
8576
|
+
|
|
8365
8577
|
const defaults$4 = {
|
|
8366
8578
|
ignoreCase: false,
|
|
8367
8579
|
requireSemicolon: true
|
|
@@ -8751,7 +8963,7 @@ class H32 extends Rule {
|
|
|
8751
8963
|
}
|
|
8752
8964
|
function isSubmit(node) {
|
|
8753
8965
|
const type = node.getAttribute("type");
|
|
8754
|
-
return Boolean(type
|
|
8966
|
+
return Boolean(!type || type.valueMatches(/submit|image/));
|
|
8755
8967
|
}
|
|
8756
8968
|
function isAssociated(id, node) {
|
|
8757
8969
|
const form = node.getAttribute("form");
|
|
@@ -8843,29 +9055,35 @@ class H37 extends Rule {
|
|
|
8843
9055
|
};
|
|
8844
9056
|
}
|
|
8845
9057
|
setup() {
|
|
8846
|
-
this.on("
|
|
8847
|
-
const
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
if (!inAccessibilityTree(node)) {
|
|
8852
|
-
return;
|
|
9058
|
+
this.on("dom:ready", (event) => {
|
|
9059
|
+
const { document } = event;
|
|
9060
|
+
const nodes = document.querySelectorAll("img, input");
|
|
9061
|
+
for (const node of nodes) {
|
|
9062
|
+
this.validateNode(node);
|
|
8853
9063
|
}
|
|
8854
|
-
|
|
9064
|
+
});
|
|
9065
|
+
}
|
|
9066
|
+
validateNode(node) {
|
|
9067
|
+
if (!needsAlt(node)) {
|
|
9068
|
+
return;
|
|
9069
|
+
}
|
|
9070
|
+
if (!inAccessibilityTree(node)) {
|
|
9071
|
+
return;
|
|
9072
|
+
}
|
|
9073
|
+
if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
|
|
9074
|
+
return;
|
|
9075
|
+
}
|
|
9076
|
+
for (const attr of this.options.alias) {
|
|
9077
|
+
if (node.getAttribute(attr)) {
|
|
8855
9078
|
return;
|
|
8856
9079
|
}
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
|
|
8865
|
-
} else {
|
|
8866
|
-
this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
|
|
8867
|
-
}
|
|
8868
|
-
});
|
|
9080
|
+
}
|
|
9081
|
+
if (node.hasAttribute("alt")) {
|
|
9082
|
+
const attr = node.getAttribute("alt");
|
|
9083
|
+
this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
|
|
9084
|
+
} else {
|
|
9085
|
+
this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
|
|
9086
|
+
}
|
|
8869
9087
|
}
|
|
8870
9088
|
}
|
|
8871
9089
|
|
|
@@ -8956,7 +9174,7 @@ class H71 extends Rule {
|
|
|
8956
9174
|
}
|
|
8957
9175
|
}
|
|
8958
9176
|
|
|
8959
|
-
const bundledRules$
|
|
9177
|
+
const bundledRules$1 = {
|
|
8960
9178
|
"wcag/h30": H30,
|
|
8961
9179
|
"wcag/h32": H32,
|
|
8962
9180
|
"wcag/h36": H36,
|
|
@@ -8965,7 +9183,7 @@ const bundledRules$2 = {
|
|
|
8965
9183
|
"wcag/h67": H67,
|
|
8966
9184
|
"wcag/h71": H71
|
|
8967
9185
|
};
|
|
8968
|
-
var WCAG = bundledRules$
|
|
9186
|
+
var WCAG = bundledRules$1;
|
|
8969
9187
|
|
|
8970
9188
|
const bundledRules = {
|
|
8971
9189
|
"allowed-links": AllowedLinks,
|
|
@@ -9001,6 +9219,7 @@ const bundledRules = {
|
|
|
9001
9219
|
"empty-title": EmptyTitle,
|
|
9002
9220
|
"form-dup-name": FormDupName,
|
|
9003
9221
|
"heading-level": HeadingLevel,
|
|
9222
|
+
"hidden-focusable": HiddenFocusable,
|
|
9004
9223
|
"id-pattern": IdPattern,
|
|
9005
9224
|
"input-attributes": InputAttributes,
|
|
9006
9225
|
"input-missing-label": InputMissingLabel,
|
|
@@ -9041,13 +9260,13 @@ const bundledRules = {
|
|
|
9041
9260
|
"svg-focusable": SvgFocusable,
|
|
9042
9261
|
"tel-non-breaking": TelNonBreaking,
|
|
9043
9262
|
"text-content": TextContent,
|
|
9263
|
+
"unique-landmark": UniqueLandmark,
|
|
9044
9264
|
"unrecognized-char-ref": UnknownCharReference,
|
|
9045
9265
|
"valid-id": ValidID,
|
|
9046
9266
|
"void-content": VoidContent,
|
|
9047
9267
|
"void-style": VoidStyle,
|
|
9048
9268
|
...WCAG
|
|
9049
9269
|
};
|
|
9050
|
-
var bundledRules$1 = bundledRules;
|
|
9051
9270
|
|
|
9052
9271
|
var defaultConfig = {};
|
|
9053
9272
|
|
|
@@ -9059,6 +9278,7 @@ const config$4 = {
|
|
|
9059
9278
|
"deprecated-rule": "warn",
|
|
9060
9279
|
"empty-heading": "error",
|
|
9061
9280
|
"empty-title": "error",
|
|
9281
|
+
"hidden-focusable": "error",
|
|
9062
9282
|
"meta-refresh": "error",
|
|
9063
9283
|
"multiple-labeled-controls": "error",
|
|
9064
9284
|
"no-autoplay": ["error", { include: ["audio", "video"] }],
|
|
@@ -9070,6 +9290,7 @@ const config$4 = {
|
|
|
9070
9290
|
"prefer-native-element": "error",
|
|
9071
9291
|
"svg-focusable": "off",
|
|
9072
9292
|
"text-content": "error",
|
|
9293
|
+
"unique-landmark": "error",
|
|
9073
9294
|
"wcag/h30": "error",
|
|
9074
9295
|
"wcag/h32": "error",
|
|
9075
9296
|
"wcag/h36": "error",
|
|
@@ -9132,6 +9353,7 @@ const config$1 = {
|
|
|
9132
9353
|
"empty-heading": "error",
|
|
9133
9354
|
"empty-title": "error",
|
|
9134
9355
|
"form-dup-name": "error",
|
|
9356
|
+
"hidden-focusable": "error",
|
|
9135
9357
|
"input-attributes": "error",
|
|
9136
9358
|
"long-title": "error",
|
|
9137
9359
|
"map-dup-name": "error",
|
|
@@ -9164,6 +9386,7 @@ const config$1 = {
|
|
|
9164
9386
|
"svg-focusable": "off",
|
|
9165
9387
|
"tel-non-breaking": "error",
|
|
9166
9388
|
"text-content": "error",
|
|
9389
|
+
"unique-landmark": "error",
|
|
9167
9390
|
"unrecognized-char-ref": "error",
|
|
9168
9391
|
"valid-id": ["error", { relaxed: false }],
|
|
9169
9392
|
void: "off",
|
|
@@ -9508,7 +9731,7 @@ class Config {
|
|
|
9508
9731
|
if (configData.rules) {
|
|
9509
9732
|
const normalizedRules = Config.getRulesObject(configData.rules);
|
|
9510
9733
|
for (const [ruleId, [, ruleOptions]] of normalizedRules.entries()) {
|
|
9511
|
-
const cls = bundledRules
|
|
9734
|
+
const cls = bundledRules[ruleId];
|
|
9512
9735
|
const path = `/rules/${ruleId}/1`;
|
|
9513
9736
|
Rule.validateOptions(cls, ruleId, path, ruleOptions, filename, configData);
|
|
9514
9737
|
}
|
|
@@ -9881,16 +10104,17 @@ class EventHandler {
|
|
|
9881
10104
|
* @returns Unregistration function.
|
|
9882
10105
|
*/
|
|
9883
10106
|
on(event, callback) {
|
|
9884
|
-
const
|
|
10107
|
+
const { listeners } = this;
|
|
10108
|
+
const names = event.split(",").map((it) => it.trim());
|
|
9885
10109
|
for (const name of names) {
|
|
9886
|
-
|
|
9887
|
-
|
|
10110
|
+
const list = listeners[name] ?? [];
|
|
10111
|
+
listeners[name] = list;
|
|
10112
|
+
list.push(callback);
|
|
9888
10113
|
}
|
|
9889
10114
|
return () => {
|
|
9890
10115
|
for (const name of names) {
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
});
|
|
10116
|
+
const list = listeners[name];
|
|
10117
|
+
this.listeners[name] = list.filter((fn) => fn !== callback);
|
|
9894
10118
|
}
|
|
9895
10119
|
};
|
|
9896
10120
|
}
|
|
@@ -9916,10 +10140,15 @@ class EventHandler {
|
|
|
9916
10140
|
* @param data - Event data.
|
|
9917
10141
|
*/
|
|
9918
10142
|
trigger(event, data) {
|
|
9919
|
-
const
|
|
9920
|
-
callbacks.forEach((listener) => {
|
|
10143
|
+
for (const listener of this.getCallbacks(event)) {
|
|
9921
10144
|
listener.call(null, event, data);
|
|
9922
|
-
}
|
|
10145
|
+
}
|
|
10146
|
+
}
|
|
10147
|
+
getCallbacks(event) {
|
|
10148
|
+
const { listeners } = this;
|
|
10149
|
+
const callbacks = listeners[event] ?? [];
|
|
10150
|
+
const global = listeners["*"] ?? [];
|
|
10151
|
+
return [...callbacks, ...global];
|
|
9923
10152
|
}
|
|
9924
10153
|
}
|
|
9925
10154
|
|
|
@@ -9989,10 +10218,10 @@ class Parser {
|
|
|
9989
10218
|
location: null
|
|
9990
10219
|
});
|
|
9991
10220
|
this.dom = new DOMTree({
|
|
9992
|
-
filename: source.filename
|
|
9993
|
-
offset: source.offset
|
|
9994
|
-
line: source.line
|
|
9995
|
-
column: source.column
|
|
10221
|
+
filename: source.filename,
|
|
10222
|
+
offset: source.offset,
|
|
10223
|
+
line: source.line,
|
|
10224
|
+
column: source.column,
|
|
9996
10225
|
size: 0
|
|
9997
10226
|
});
|
|
9998
10227
|
this.trigger("dom:load", {
|
|
@@ -10516,7 +10745,7 @@ function isThenable(value) {
|
|
|
10516
10745
|
return value && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
10517
10746
|
}
|
|
10518
10747
|
|
|
10519
|
-
const ruleIds = new Set(Object.keys(bundledRules
|
|
10748
|
+
const ruleIds = new Set(Object.keys(bundledRules));
|
|
10520
10749
|
function ruleExists(ruleId) {
|
|
10521
10750
|
return ruleIds.has(ruleId);
|
|
10522
10751
|
}
|
|
@@ -10605,9 +10834,7 @@ class Reporter {
|
|
|
10605
10834
|
valid: this.isValid(),
|
|
10606
10835
|
results: Object.keys(this.result).map((filePath) => {
|
|
10607
10836
|
const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
|
|
10608
|
-
const source = (sources ?? []).find(
|
|
10609
|
-
(source2) => filePath === (source2.filename ?? "")
|
|
10610
|
-
);
|
|
10837
|
+
const source = (sources ?? []).find((source2) => filePath === source2.filename);
|
|
10611
10838
|
return {
|
|
10612
10839
|
filePath,
|
|
10613
10840
|
messages,
|
|
@@ -10675,7 +10902,7 @@ class Engine {
|
|
|
10675
10902
|
this.ParserClass = ParserClass;
|
|
10676
10903
|
const result = this.initPlugins(this.config);
|
|
10677
10904
|
this.availableRules = {
|
|
10678
|
-
...bundledRules
|
|
10905
|
+
...bundledRules,
|
|
10679
10906
|
...result.availableRules
|
|
10680
10907
|
};
|
|
10681
10908
|
}
|
|
@@ -11104,10 +11331,11 @@ class HtmlValidate {
|
|
|
11104
11331
|
* @returns Report output.
|
|
11105
11332
|
*/
|
|
11106
11333
|
async validateSource(input, configOverride) {
|
|
11107
|
-
const
|
|
11108
|
-
const
|
|
11334
|
+
const source = normalizeSource(input);
|
|
11335
|
+
const config = await this.getConfigFor(source.filename, configOverride);
|
|
11336
|
+
const transformedSource = config.transformSource(source);
|
|
11109
11337
|
const engine = new Engine(config, Parser);
|
|
11110
|
-
return engine.lint(
|
|
11338
|
+
return engine.lint(transformedSource);
|
|
11111
11339
|
}
|
|
11112
11340
|
/**
|
|
11113
11341
|
* Parse and validate HTML from [[Source]].
|
|
@@ -11117,10 +11345,11 @@ class HtmlValidate {
|
|
|
11117
11345
|
* @returns Report output.
|
|
11118
11346
|
*/
|
|
11119
11347
|
validateSourceSync(input, configOverride) {
|
|
11120
|
-
const
|
|
11121
|
-
const
|
|
11348
|
+
const source = normalizeSource(input);
|
|
11349
|
+
const config = this.getConfigForSync(source.filename, configOverride);
|
|
11350
|
+
const transformedSource = config.transformSource(source);
|
|
11122
11351
|
const engine = new Engine(config, Parser);
|
|
11123
|
-
return engine.lint(
|
|
11352
|
+
return engine.lint(transformedSource);
|
|
11124
11353
|
}
|
|
11125
11354
|
/**
|
|
11126
11355
|
* Parse and validate HTML from file.
|
|
@@ -11429,7 +11658,7 @@ class HtmlValidate {
|
|
|
11429
11658
|
}
|
|
11430
11659
|
|
|
11431
11660
|
const name = "html-validate";
|
|
11432
|
-
const version = "8.
|
|
11661
|
+
const version = "8.9.0";
|
|
11433
11662
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
11434
11663
|
|
|
11435
11664
|
function definePlugin(plugin) {
|