html-validate 8.20.1 → 8.22.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/browser.js +1 -1
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core-nodejs.js.map +1 -1
- package/dist/cjs/core.js +1781 -1613
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/elements.js +4 -6
- package/dist/cjs/elements.js.map +1 -1
- package/dist/cjs/html-validate.js +0 -1
- package/dist/cjs/html-validate.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/jest-diff.js.map +1 -1
- package/dist/cjs/jest.js +0 -1
- package/dist/cjs/jest.js.map +1 -1
- package/dist/cjs/matcher-utils.js.map +1 -1
- package/dist/cjs/matchers.js.map +1 -1
- package/dist/cjs/tsdoc-metadata.json +1 -1
- package/dist/cjs/vitest.js +0 -1
- package/dist/cjs/vitest.js.map +1 -1
- package/dist/es/browser.js +1 -2
- package/dist/es/browser.js.map +1 -1
- package/dist/es/cli.js.map +1 -1
- package/dist/es/core-browser.js +1 -1
- package/dist/es/core-nodejs.js +1 -1
- package/dist/es/core-nodejs.js.map +1 -1
- package/dist/es/core.js +1781 -1614
- package/dist/es/core.js.map +1 -1
- package/dist/es/elements.js +4 -6
- package/dist/es/elements.js.map +1 -1
- package/dist/es/html-validate.js +1 -2
- package/dist/es/html-validate.js.map +1 -1
- package/dist/es/index.js +1 -2
- package/dist/es/index.js.map +1 -1
- package/dist/es/jest-diff.js.map +1 -1
- package/dist/es/jest.js +0 -1
- package/dist/es/jest.js.map +1 -1
- package/dist/es/matcher-utils.js.map +1 -1
- package/dist/es/matchers-jestonly.js +1 -1
- package/dist/es/matchers.js.map +1 -1
- package/dist/es/vitest.js +0 -1
- package/dist/es/vitest.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types/browser.d.ts +65 -18
- package/dist/types/index.d.ts +65 -18
- package/package.json +23 -27
package/dist/es/core.js
CHANGED
|
@@ -3,7 +3,6 @@ import { e as entities$1, h as html5, b as bundledElements } from './elements.js
|
|
|
3
3
|
import betterAjvErrors from '@sidvind/better-ajv-errors';
|
|
4
4
|
import { n as naturalJoin } from './utils/natural-join.js';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
-
import { codeFrameColumns } from '@babel/code-frame';
|
|
7
6
|
import kleur from 'kleur';
|
|
8
7
|
import { stylish as stylish$1 } from '@html-validate/stylish';
|
|
9
8
|
import semver from 'semver';
|
|
@@ -1269,14 +1268,12 @@ class MetaTable {
|
|
|
1269
1268
|
* @returns A shallow copy of metadata.
|
|
1270
1269
|
*/
|
|
1271
1270
|
getMetaFor(tagName) {
|
|
1272
|
-
|
|
1273
|
-
if (
|
|
1274
|
-
return { ...
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
return { ...this.elements["*"] };
|
|
1271
|
+
const meta = this.elements[tagName.toLowerCase()] ?? this.elements["*"];
|
|
1272
|
+
if (meta) {
|
|
1273
|
+
return { ...meta };
|
|
1274
|
+
} else {
|
|
1275
|
+
return null;
|
|
1278
1276
|
}
|
|
1279
|
-
return null;
|
|
1280
1277
|
}
|
|
1281
1278
|
/**
|
|
1282
1279
|
* Find all tags which has enabled given property.
|
|
@@ -1284,7 +1281,7 @@ class MetaTable {
|
|
|
1284
1281
|
* @public
|
|
1285
1282
|
*/
|
|
1286
1283
|
getTagsWithProperty(propName) {
|
|
1287
|
-
return
|
|
1284
|
+
return this.entries.filter(([, entry]) => entry[propName]).map(([tagName]) => tagName);
|
|
1288
1285
|
}
|
|
1289
1286
|
/**
|
|
1290
1287
|
* Find tag matching tagName or inheriting from it.
|
|
@@ -1292,10 +1289,10 @@ class MetaTable {
|
|
|
1292
1289
|
* @public
|
|
1293
1290
|
*/
|
|
1294
1291
|
getTagsDerivedFrom(tagName) {
|
|
1295
|
-
return
|
|
1292
|
+
return this.entries.filter(([key, entry]) => key === tagName || entry.inherit === tagName).map(([tagName2]) => tagName2);
|
|
1296
1293
|
}
|
|
1297
1294
|
addEntry(tagName, entry) {
|
|
1298
|
-
let parent = this.elements[tagName]
|
|
1295
|
+
let parent = this.elements[tagName];
|
|
1299
1296
|
if (entry.inherit) {
|
|
1300
1297
|
const name = entry.inherit;
|
|
1301
1298
|
parent = this.elements[name];
|
|
@@ -1306,7 +1303,7 @@ class MetaTable {
|
|
|
1306
1303
|
});
|
|
1307
1304
|
}
|
|
1308
1305
|
}
|
|
1309
|
-
const expanded = this.mergeElement(parent, { ...entry, tagName });
|
|
1306
|
+
const expanded = this.mergeElement(parent ?? {}, { ...entry, tagName });
|
|
1310
1307
|
expandRegex(expanded);
|
|
1311
1308
|
this.elements[tagName] = expanded;
|
|
1312
1309
|
}
|
|
@@ -1335,6 +1332,12 @@ class MetaTable {
|
|
|
1335
1332
|
getJSONSchema() {
|
|
1336
1333
|
return this.schema;
|
|
1337
1334
|
}
|
|
1335
|
+
/**
|
|
1336
|
+
* @internal
|
|
1337
|
+
*/
|
|
1338
|
+
get entries() {
|
|
1339
|
+
return Object.entries(this.elements);
|
|
1340
|
+
}
|
|
1338
1341
|
/**
|
|
1339
1342
|
* Finds the global element definition and merges each known element with the
|
|
1340
1343
|
* global, e.g. to assign global attributes.
|
|
@@ -1346,7 +1349,7 @@ class MetaTable {
|
|
|
1346
1349
|
delete this.elements["*"];
|
|
1347
1350
|
delete global.tagName;
|
|
1348
1351
|
delete global.void;
|
|
1349
|
-
for (const [tagName, entry] of
|
|
1352
|
+
for (const [tagName, entry] of this.entries) {
|
|
1350
1353
|
this.elements[tagName] = this.mergeElement(global, entry);
|
|
1351
1354
|
}
|
|
1352
1355
|
}
|
|
@@ -1667,6 +1670,7 @@ class DOMNode {
|
|
|
1667
1670
|
/**
|
|
1668
1671
|
* Create a new DOMNode.
|
|
1669
1672
|
*
|
|
1673
|
+
* @internal
|
|
1670
1674
|
* @param nodeType - What node type to create.
|
|
1671
1675
|
* @param nodeName - What node name to use. For `HtmlElement` this corresponds
|
|
1672
1676
|
* to the tagName but other node types have specific predefined values.
|
|
@@ -2134,7 +2138,7 @@ class AttrMatcher extends Matcher {
|
|
|
2134
2138
|
this.value = value;
|
|
2135
2139
|
}
|
|
2136
2140
|
match(node) {
|
|
2137
|
-
const attr = node.getAttribute(this.key, true)
|
|
2141
|
+
const attr = node.getAttribute(this.key, true);
|
|
2138
2142
|
return attr.some((cur) => {
|
|
2139
2143
|
switch (this.op) {
|
|
2140
2144
|
case void 0:
|
|
@@ -2335,8 +2339,15 @@ function createAdapter(node) {
|
|
|
2335
2339
|
};
|
|
2336
2340
|
}
|
|
2337
2341
|
class HtmlElement extends DOMNode {
|
|
2338
|
-
constructor(
|
|
2339
|
-
const
|
|
2342
|
+
constructor(details) {
|
|
2343
|
+
const {
|
|
2344
|
+
nodeType,
|
|
2345
|
+
tagName,
|
|
2346
|
+
parent = null,
|
|
2347
|
+
closed = 1 /* EndTag */,
|
|
2348
|
+
meta = null,
|
|
2349
|
+
location
|
|
2350
|
+
} = details;
|
|
2340
2351
|
super(nodeType, tagName, location);
|
|
2341
2352
|
if (isInvalidTagName(tagName)) {
|
|
2342
2353
|
throw new Error(`The tag name provided ("${tagName}") is not a valid name`);
|
|
@@ -2359,11 +2370,39 @@ class HtmlElement extends DOMNode {
|
|
|
2359
2370
|
}
|
|
2360
2371
|
}
|
|
2361
2372
|
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Manually create a new element. This is primary useful for test-cases. While
|
|
2375
|
+
* the API is public it is not meant for general consumption and is not
|
|
2376
|
+
* guaranteed to be stable across versions.
|
|
2377
|
+
*
|
|
2378
|
+
* Use at your own risk. Prefer to use [[Parser]] to parse a string of markup
|
|
2379
|
+
* instead.
|
|
2380
|
+
*
|
|
2381
|
+
* @public
|
|
2382
|
+
* @since 8.22.0
|
|
2383
|
+
* @param tagName - Element tagname.
|
|
2384
|
+
* @param location - Element location.
|
|
2385
|
+
* @param details - Additional element details.
|
|
2386
|
+
*/
|
|
2387
|
+
static createElement(tagName, location, details = {}) {
|
|
2388
|
+
const { closed = 1 /* EndTag */, meta = null, parent = null } = details;
|
|
2389
|
+
return new HtmlElement({
|
|
2390
|
+
nodeType: NodeType.ELEMENT_NODE,
|
|
2391
|
+
tagName,
|
|
2392
|
+
parent,
|
|
2393
|
+
closed,
|
|
2394
|
+
meta,
|
|
2395
|
+
location
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2362
2398
|
/**
|
|
2363
2399
|
* @internal
|
|
2364
2400
|
*/
|
|
2365
2401
|
static rootNode(location) {
|
|
2366
|
-
const root = new HtmlElement(
|
|
2402
|
+
const root = new HtmlElement({
|
|
2403
|
+
nodeType: NodeType.DOCUMENT_NODE,
|
|
2404
|
+
location
|
|
2405
|
+
});
|
|
2367
2406
|
root.setAnnotation("#document");
|
|
2368
2407
|
return root;
|
|
2369
2408
|
}
|
|
@@ -2382,7 +2421,14 @@ class HtmlElement extends DOMNode {
|
|
|
2382
2421
|
const open = startToken.data[1] !== "/";
|
|
2383
2422
|
const closed = isClosed(endToken, meta);
|
|
2384
2423
|
const location = sliceLocation(startToken.location, 1);
|
|
2385
|
-
return new HtmlElement(
|
|
2424
|
+
return new HtmlElement({
|
|
2425
|
+
nodeType: NodeType.ELEMENT_NODE,
|
|
2426
|
+
tagName,
|
|
2427
|
+
parent: open ? parent : null,
|
|
2428
|
+
closed,
|
|
2429
|
+
meta,
|
|
2430
|
+
location
|
|
2431
|
+
});
|
|
2386
2432
|
}
|
|
2387
2433
|
/**
|
|
2388
2434
|
* Returns annotated name if set or defaults to `<tagName>`.
|
|
@@ -2580,10 +2626,13 @@ class HtmlElement extends DOMNode {
|
|
|
2580
2626
|
*/
|
|
2581
2627
|
setAttribute(key, value, keyLocation, valueLocation, originalAttribute) {
|
|
2582
2628
|
key = key.toLowerCase();
|
|
2583
|
-
|
|
2584
|
-
|
|
2629
|
+
const attr = new Attribute(key, value, keyLocation, valueLocation, originalAttribute);
|
|
2630
|
+
const list = this.attr[key];
|
|
2631
|
+
if (list) {
|
|
2632
|
+
list.push(attr);
|
|
2633
|
+
} else {
|
|
2634
|
+
this.attr[key] = [attr];
|
|
2585
2635
|
}
|
|
2586
|
-
this.attr[key].push(new Attribute(key, value, keyLocation, valueLocation, originalAttribute));
|
|
2587
2636
|
}
|
|
2588
2637
|
/**
|
|
2589
2638
|
* Get parsed tabindex for this element.
|
|
@@ -2640,7 +2689,7 @@ class HtmlElement extends DOMNode {
|
|
|
2640
2689
|
const matches = this.attr[key];
|
|
2641
2690
|
return all ? matches : matches[0];
|
|
2642
2691
|
} else {
|
|
2643
|
-
return null;
|
|
2692
|
+
return all ? [] : null;
|
|
2644
2693
|
}
|
|
2645
2694
|
}
|
|
2646
2695
|
/**
|
|
@@ -2746,20 +2795,6 @@ class HtmlElement extends DOMNode {
|
|
|
2746
2795
|
yield* pattern.match(this);
|
|
2747
2796
|
}
|
|
2748
2797
|
}
|
|
2749
|
-
/**
|
|
2750
|
-
* Visit all nodes from this node and down. Depth first.
|
|
2751
|
-
*
|
|
2752
|
-
* @internal
|
|
2753
|
-
*/
|
|
2754
|
-
visitDepthFirst(callback) {
|
|
2755
|
-
function visit(node) {
|
|
2756
|
-
node.childElements.forEach(visit);
|
|
2757
|
-
if (!node.isRootElement()) {
|
|
2758
|
-
callback(node);
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
visit(this);
|
|
2762
|
-
}
|
|
2763
2798
|
/**
|
|
2764
2799
|
* Evaluates callbackk on all descendants, returning true if any are true.
|
|
2765
2800
|
*
|
|
@@ -2823,738 +2858,309 @@ function isClosed(endToken, meta) {
|
|
|
2823
2858
|
return closed;
|
|
2824
2859
|
}
|
|
2825
2860
|
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
pushActive(node) {
|
|
2833
|
-
this.active = node;
|
|
2834
|
-
}
|
|
2835
|
-
popActive() {
|
|
2836
|
-
if (this.active.isRootElement()) {
|
|
2837
|
-
return;
|
|
2861
|
+
function dumpTree(root) {
|
|
2862
|
+
const lines = [];
|
|
2863
|
+
function decoration(node) {
|
|
2864
|
+
let output = "";
|
|
2865
|
+
if (node.id) {
|
|
2866
|
+
output += `#${node.id}`;
|
|
2838
2867
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
return
|
|
2868
|
+
if (node.hasAttribute("class")) {
|
|
2869
|
+
output += `.${node.classList.join(".")}`;
|
|
2870
|
+
}
|
|
2871
|
+
return output;
|
|
2843
2872
|
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2873
|
+
function writeNode(node, level, sibling) {
|
|
2874
|
+
if (node.parent) {
|
|
2875
|
+
const indent = " ".repeat(level - 1);
|
|
2876
|
+
const l = node.childElements.length > 0 ? "\u252C" : "\u2500";
|
|
2877
|
+
const b = sibling < node.parent.childElements.length - 1 ? "\u251C" : "\u2514";
|
|
2878
|
+
lines.push(`${indent}${b}\u2500${l} ${node.tagName}${decoration(node)}`);
|
|
2879
|
+
} else {
|
|
2880
|
+
lines.push("(root)");
|
|
2881
|
+
}
|
|
2882
|
+
node.childElements.forEach((child, index) => {
|
|
2883
|
+
writeNode(child, level + 1, index);
|
|
2850
2884
|
});
|
|
2851
2885
|
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2886
|
+
writeNode(root, 0, 0);
|
|
2887
|
+
return lines;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
function escape(value) {
|
|
2891
|
+
return JSON.stringify(value);
|
|
2892
|
+
}
|
|
2893
|
+
function format(value, quote = false) {
|
|
2894
|
+
if (value === null) {
|
|
2895
|
+
return "null";
|
|
2854
2896
|
}
|
|
2855
|
-
|
|
2856
|
-
|
|
2897
|
+
if (typeof value === "number") {
|
|
2898
|
+
return value.toString();
|
|
2857
2899
|
}
|
|
2858
|
-
|
|
2859
|
-
return
|
|
2900
|
+
if (typeof value === "string") {
|
|
2901
|
+
return quote ? escape(value) : value;
|
|
2860
2902
|
}
|
|
2861
|
-
|
|
2862
|
-
|
|
2903
|
+
if (Array.isArray(value)) {
|
|
2904
|
+
const content = value.map((it) => format(it, true)).join(", ");
|
|
2905
|
+
return `[ ${content} ]`;
|
|
2863
2906
|
}
|
|
2864
|
-
|
|
2865
|
-
|
|
2907
|
+
if (typeof value === "object") {
|
|
2908
|
+
const content = Object.entries(value).map(([key, nested]) => `${key}: ${format(nested, true)}`).join(", ");
|
|
2909
|
+
return `{ ${content} }`;
|
|
2866
2910
|
}
|
|
2911
|
+
return String(value);
|
|
2912
|
+
}
|
|
2913
|
+
function interpolate(text, data) {
|
|
2914
|
+
return text.replace(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
|
|
2915
|
+
return typeof data[key] !== "undefined" ? format(data[key]) : match;
|
|
2916
|
+
});
|
|
2867
2917
|
}
|
|
2868
2918
|
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
return
|
|
2884
|
-
|
|
2919
|
+
function isThenable(value) {
|
|
2920
|
+
return value && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
var Severity = /* @__PURE__ */ ((Severity2) => {
|
|
2924
|
+
Severity2[Severity2["DISABLED"] = 0] = "DISABLED";
|
|
2925
|
+
Severity2[Severity2["WARN"] = 1] = "WARN";
|
|
2926
|
+
Severity2[Severity2["ERROR"] = 2] = "ERROR";
|
|
2927
|
+
return Severity2;
|
|
2928
|
+
})(Severity || {});
|
|
2929
|
+
function parseSeverity(value) {
|
|
2930
|
+
switch (value) {
|
|
2931
|
+
case 0:
|
|
2932
|
+
case "off":
|
|
2933
|
+
return 0 /* DISABLED */;
|
|
2934
|
+
case 1:
|
|
2935
|
+
case "warn":
|
|
2936
|
+
return 1 /* WARN */;
|
|
2937
|
+
case 2:
|
|
2938
|
+
case "error":
|
|
2939
|
+
return 2 /* ERROR */;
|
|
2940
|
+
default:
|
|
2941
|
+
throw new Error(`Invalid severity "${String(value)}"`);
|
|
2885
2942
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
for (const child of siblings.slice(limit)) {
|
|
2915
|
-
cb(child, category);
|
|
2916
|
-
}
|
|
2917
|
-
valid = false;
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
return valid;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
const cacheKey = Symbol("aria-naming");
|
|
2946
|
+
const defaultValue = "allowed";
|
|
2947
|
+
const prohibitedRoles = [
|
|
2948
|
+
"caption",
|
|
2949
|
+
"code",
|
|
2950
|
+
"deletion",
|
|
2951
|
+
"emphasis",
|
|
2952
|
+
"generic",
|
|
2953
|
+
"insertion",
|
|
2954
|
+
"paragraph",
|
|
2955
|
+
"presentation",
|
|
2956
|
+
"strong",
|
|
2957
|
+
"subscript",
|
|
2958
|
+
"superscript"
|
|
2959
|
+
];
|
|
2960
|
+
function byRole(role) {
|
|
2961
|
+
return prohibitedRoles.includes(role) ? "prohibited" : "allowed";
|
|
2962
|
+
}
|
|
2963
|
+
function byMeta(element, meta) {
|
|
2964
|
+
return meta.aria.naming(element._adapter);
|
|
2965
|
+
}
|
|
2966
|
+
function ariaNaming(element) {
|
|
2967
|
+
var _a;
|
|
2968
|
+
const cached = element.cacheGet(cacheKey);
|
|
2969
|
+
if (cached) {
|
|
2970
|
+
return cached;
|
|
2922
2971
|
}
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
*
|
|
2930
|
-
* For instance, for a `<table>` element the `<caption>` element must come
|
|
2931
|
-
* before a `<thead>` which must come before `<tbody>`.
|
|
2932
|
-
*
|
|
2933
|
-
* @param children - Array of children to validate.
|
|
2934
|
-
*/
|
|
2935
|
-
static validateOrder(children, rules, cb) {
|
|
2936
|
-
if (!rules) {
|
|
2937
|
-
return true;
|
|
2938
|
-
}
|
|
2939
|
-
let i = 0;
|
|
2940
|
-
let prev = null;
|
|
2941
|
-
for (const node of children) {
|
|
2942
|
-
const old = i;
|
|
2943
|
-
while (rules[i] && !Validator.validatePermittedCategory(node, rules[i], true)) {
|
|
2944
|
-
i++;
|
|
2945
|
-
}
|
|
2946
|
-
if (i >= rules.length) {
|
|
2947
|
-
const orderSpecified = rules.find(
|
|
2948
|
-
(cur) => Validator.validatePermittedCategory(node, cur, true)
|
|
2949
|
-
);
|
|
2950
|
-
if (orderSpecified) {
|
|
2951
|
-
cb(node, prev);
|
|
2952
|
-
return false;
|
|
2953
|
-
}
|
|
2954
|
-
i = old;
|
|
2955
|
-
}
|
|
2956
|
-
prev = node;
|
|
2972
|
+
const role = (_a = element.getAttribute("role")) == null ? void 0 : _a.value;
|
|
2973
|
+
if (role) {
|
|
2974
|
+
if (role instanceof DynamicValue) {
|
|
2975
|
+
return element.cacheSet(cacheKey, defaultValue);
|
|
2976
|
+
} else {
|
|
2977
|
+
return element.cacheSet(cacheKey, byRole(role));
|
|
2957
2978
|
}
|
|
2958
|
-
return true;
|
|
2959
2979
|
}
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
* Check if an element has the required set of elements. At least one of the
|
|
2964
|
-
* selectors must match.
|
|
2965
|
-
*/
|
|
2966
|
-
static validateAncestors(node, rules) {
|
|
2967
|
-
if (!rules || rules.length === 0) {
|
|
2968
|
-
return true;
|
|
2969
|
-
}
|
|
2970
|
-
return rules.some((rule) => node.closest(rule));
|
|
2980
|
+
const meta = element.meta;
|
|
2981
|
+
if (!meta) {
|
|
2982
|
+
return element.cacheSet(cacheKey, defaultValue);
|
|
2971
2983
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
(child) => Validator.validatePermittedCategory(child, tagName, false)
|
|
2988
|
-
);
|
|
2989
|
-
return !haveMatchingChild;
|
|
2990
|
-
});
|
|
2984
|
+
return element.cacheSet(cacheKey, byMeta(element, meta));
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
const patternCache = /* @__PURE__ */ new Map();
|
|
2988
|
+
function compileStringPattern(pattern) {
|
|
2989
|
+
const regexp = pattern.replace(/[*]+/g, ".+");
|
|
2990
|
+
return new RegExp(`^${regexp}$`);
|
|
2991
|
+
}
|
|
2992
|
+
function compileRegExpPattern(pattern) {
|
|
2993
|
+
return new RegExp(`^${pattern}$`);
|
|
2994
|
+
}
|
|
2995
|
+
function compilePattern(pattern) {
|
|
2996
|
+
const cached = patternCache.get(pattern);
|
|
2997
|
+
if (cached) {
|
|
2998
|
+
return cached;
|
|
2991
2999
|
}
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
if (!rule) {
|
|
3002
|
-
return true;
|
|
3003
|
-
}
|
|
3004
|
-
const value = attr.value;
|
|
3005
|
-
if (value instanceof DynamicValue) {
|
|
3006
|
-
return true;
|
|
3007
|
-
}
|
|
3008
|
-
const empty = value === null || value === "";
|
|
3009
|
-
if (rule.boolean) {
|
|
3010
|
-
return empty || value === attr.key;
|
|
3011
|
-
}
|
|
3012
|
-
if (rule.omit && empty) {
|
|
3000
|
+
const match = pattern.match(/^\/(.*)\/$/);
|
|
3001
|
+
const regexp = match ? compileRegExpPattern(match[1]) : compileStringPattern(pattern);
|
|
3002
|
+
patternCache.set(pattern, regexp);
|
|
3003
|
+
return regexp;
|
|
3004
|
+
}
|
|
3005
|
+
function keywordPatternMatcher(list, keyword) {
|
|
3006
|
+
for (const pattern of list) {
|
|
3007
|
+
const regexp = compilePattern(pattern);
|
|
3008
|
+
if (regexp.test(keyword)) {
|
|
3013
3009
|
return true;
|
|
3014
3010
|
}
|
|
3015
|
-
if (rule.list) {
|
|
3016
|
-
const tokens = new DOMTokenList(value, attr.valueLocation);
|
|
3017
|
-
return tokens.every((token) => {
|
|
3018
|
-
return this.validateAttributeValue(token, rule);
|
|
3019
|
-
});
|
|
3020
|
-
}
|
|
3021
|
-
return this.validateAttributeValue(value, rule);
|
|
3022
3011
|
}
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
}
|
|
3030
|
-
const caseInsensitiveValue = value.toLowerCase();
|
|
3031
|
-
return rule.enum.some((entry) => {
|
|
3032
|
-
if (entry instanceof RegExp) {
|
|
3033
|
-
return !!value.match(entry);
|
|
3034
|
-
} else {
|
|
3035
|
-
return caseInsensitiveValue === entry;
|
|
3036
|
-
}
|
|
3037
|
-
});
|
|
3012
|
+
return false;
|
|
3013
|
+
}
|
|
3014
|
+
function isKeywordIgnored(options, keyword, matcher = (list, it) => list.includes(it)) {
|
|
3015
|
+
const { include, exclude } = options;
|
|
3016
|
+
if (include && !matcher(include, keyword)) {
|
|
3017
|
+
return true;
|
|
3038
3018
|
}
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
return Validator.validatePermittedCategory(node, rule, !isExclude);
|
|
3042
|
-
} else if (Array.isArray(rule)) {
|
|
3043
|
-
return rule.every((inner) => {
|
|
3044
|
-
return Validator.validatePermittedRule(node, inner, isExclude);
|
|
3045
|
-
});
|
|
3046
|
-
} else {
|
|
3047
|
-
validateKeys(rule);
|
|
3048
|
-
if (rule.exclude) {
|
|
3049
|
-
if (Array.isArray(rule.exclude)) {
|
|
3050
|
-
return !rule.exclude.some((inner) => {
|
|
3051
|
-
return Validator.validatePermittedRule(node, inner, true);
|
|
3052
|
-
});
|
|
3053
|
-
} else {
|
|
3054
|
-
return !Validator.validatePermittedRule(node, rule.exclude, true);
|
|
3055
|
-
}
|
|
3056
|
-
} else {
|
|
3057
|
-
return true;
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3019
|
+
if (exclude && matcher(exclude, keyword)) {
|
|
3020
|
+
return true;
|
|
3060
3021
|
}
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
*/
|
|
3073
|
-
/* eslint-disable-next-line complexity -- rule does not like switch */
|
|
3074
|
-
static validatePermittedCategory(node, category, defaultMatch) {
|
|
3075
|
-
const [, rawCategory] = category.match(/^(@?.*?)([?*]?)$/);
|
|
3076
|
-
if (!rawCategory.startsWith("@")) {
|
|
3077
|
-
return node.tagName === rawCategory;
|
|
3078
|
-
}
|
|
3079
|
-
if (!node.meta) {
|
|
3080
|
-
return defaultMatch;
|
|
3081
|
-
}
|
|
3082
|
-
switch (rawCategory) {
|
|
3083
|
-
case "@meta":
|
|
3084
|
-
return node.meta.metadata;
|
|
3085
|
-
case "@flow":
|
|
3086
|
-
return node.meta.flow;
|
|
3087
|
-
case "@sectioning":
|
|
3088
|
-
return node.meta.sectioning;
|
|
3089
|
-
case "@heading":
|
|
3090
|
-
return node.meta.heading;
|
|
3091
|
-
case "@phrasing":
|
|
3092
|
-
return node.meta.phrasing;
|
|
3093
|
-
case "@embedded":
|
|
3094
|
-
return node.meta.embedded;
|
|
3095
|
-
case "@interactive":
|
|
3096
|
-
return node.meta.interactive;
|
|
3097
|
-
case "@script":
|
|
3098
|
-
return Boolean(node.meta.scriptSupporting);
|
|
3099
|
-
case "@form":
|
|
3100
|
-
return Boolean(node.meta.form);
|
|
3101
|
-
default:
|
|
3102
|
-
throw new Error(`Invalid content category "${category}"`);
|
|
3103
|
-
}
|
|
3022
|
+
return false;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
3026
|
+
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
3027
|
+
const INERT_CACHE = Symbol(isInert.name);
|
|
3028
|
+
const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
|
|
3029
|
+
const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
|
|
3030
|
+
function inAccessibilityTree(node) {
|
|
3031
|
+
if (isAriaHidden(node)) {
|
|
3032
|
+
return false;
|
|
3104
3033
|
}
|
|
3034
|
+
if (isPresentation(node)) {
|
|
3035
|
+
return false;
|
|
3036
|
+
}
|
|
3037
|
+
if (isHTMLHidden(node)) {
|
|
3038
|
+
return false;
|
|
3039
|
+
}
|
|
3040
|
+
if (isInert(node)) {
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
if (isStyleHidden(node)) {
|
|
3044
|
+
return false;
|
|
3045
|
+
}
|
|
3046
|
+
return true;
|
|
3105
3047
|
}
|
|
3106
|
-
function
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3048
|
+
function isAriaHiddenImpl(node) {
|
|
3049
|
+
const getAriaHiddenAttr = (node2) => {
|
|
3050
|
+
const ariaHidden = node2.getAttribute("aria-hidden");
|
|
3051
|
+
return Boolean(ariaHidden && ariaHidden.value === "true");
|
|
3052
|
+
};
|
|
3053
|
+
return {
|
|
3054
|
+
byParent: node.parent ? isAriaHidden(node.parent) : false,
|
|
3055
|
+
bySelf: getAriaHiddenAttr(node)
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
function isAriaHidden(node, details) {
|
|
3059
|
+
const cached = node.cacheGet(ARIA_HIDDEN_CACHE);
|
|
3060
|
+
if (cached) {
|
|
3061
|
+
return details ? cached : cached.byParent || cached.bySelf;
|
|
3112
3062
|
}
|
|
3063
|
+
const result = node.cacheSet(ARIA_HIDDEN_CACHE, isAriaHiddenImpl(node));
|
|
3064
|
+
return details ? result : result.byParent || result.bySelf;
|
|
3113
3065
|
}
|
|
3114
|
-
function
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3066
|
+
function isHTMLHiddenImpl(node) {
|
|
3067
|
+
const getHiddenAttr = (node2) => {
|
|
3068
|
+
const hidden = node2.getAttribute("hidden");
|
|
3069
|
+
return Boolean(hidden == null ? void 0 : hidden.isStatic);
|
|
3070
|
+
};
|
|
3071
|
+
return {
|
|
3072
|
+
byParent: node.parent ? isHTMLHidden(node.parent) : false,
|
|
3073
|
+
bySelf: getHiddenAttr(node)
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
function isHTMLHidden(node, details) {
|
|
3077
|
+
const cached = node.cacheGet(HTML_HIDDEN_CACHE);
|
|
3078
|
+
if (cached) {
|
|
3079
|
+
return details ? cached : cached.byParent || cached.bySelf;
|
|
3122
3080
|
}
|
|
3081
|
+
const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
|
|
3082
|
+
return details ? result : result.byParent || result.bySelf;
|
|
3123
3083
|
}
|
|
3124
|
-
|
|
3125
|
-
const
|
|
3126
|
-
const
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
},
|
|
3139
|
-
"extends": {
|
|
3140
|
-
type: "array",
|
|
3141
|
-
items: {
|
|
3142
|
-
type: "string"
|
|
3143
|
-
},
|
|
3144
|
-
title: "Configurations to extend",
|
|
3145
|
-
description: "Array of shareable or builtin configurations to extend."
|
|
3146
|
-
},
|
|
3147
|
-
elements: {
|
|
3148
|
-
type: "array",
|
|
3149
|
-
items: {
|
|
3150
|
-
anyOf: [
|
|
3151
|
-
{
|
|
3152
|
-
type: "string"
|
|
3153
|
-
},
|
|
3154
|
-
{
|
|
3155
|
-
type: "object"
|
|
3156
|
-
}
|
|
3157
|
-
]
|
|
3158
|
-
},
|
|
3159
|
-
title: "Element metadata to load",
|
|
3160
|
-
description: "Array of modules, plugins or files to load element metadata from. Use <rootDir> to refer to the folder with the package.json file.",
|
|
3161
|
-
examples: [
|
|
3162
|
-
[
|
|
3163
|
-
"html-validate:recommended",
|
|
3164
|
-
"plugin:recommended",
|
|
3165
|
-
"module",
|
|
3166
|
-
"./local-file.json"
|
|
3167
|
-
]
|
|
3168
|
-
]
|
|
3169
|
-
},
|
|
3170
|
-
plugins: {
|
|
3171
|
-
type: "array",
|
|
3172
|
-
items: {
|
|
3173
|
-
anyOf: [
|
|
3174
|
-
{
|
|
3175
|
-
type: "string"
|
|
3176
|
-
},
|
|
3177
|
-
{
|
|
3178
|
-
type: "object"
|
|
3179
|
-
}
|
|
3180
|
-
]
|
|
3181
|
-
},
|
|
3182
|
-
title: "Plugins to load",
|
|
3183
|
-
description: "Array of plugins load. Use <rootDir> to refer to the folder with the package.json file.",
|
|
3184
|
-
examples: [
|
|
3185
|
-
[
|
|
3186
|
-
"my-plugin",
|
|
3187
|
-
"./local-plugin"
|
|
3188
|
-
]
|
|
3189
|
-
]
|
|
3190
|
-
},
|
|
3191
|
-
transform: {
|
|
3192
|
-
type: "object",
|
|
3193
|
-
additionalProperties: {
|
|
3194
|
-
type: "string"
|
|
3195
|
-
},
|
|
3196
|
-
title: "File transformations to use.",
|
|
3197
|
-
description: "Object where key is regular expression to match filename and value is name of transformer.",
|
|
3198
|
-
examples: [
|
|
3199
|
-
{
|
|
3200
|
-
"^.*\\.foo$": "my-transformer",
|
|
3201
|
-
"^.*\\.bar$": "my-plugin",
|
|
3202
|
-
"^.*\\.baz$": "my-plugin:named"
|
|
3203
|
-
}
|
|
3204
|
-
]
|
|
3205
|
-
},
|
|
3206
|
-
rules: {
|
|
3207
|
-
type: "object",
|
|
3208
|
-
patternProperties: {
|
|
3209
|
-
".*": {
|
|
3210
|
-
anyOf: [
|
|
3211
|
-
{
|
|
3212
|
-
"enum": [
|
|
3213
|
-
0,
|
|
3214
|
-
1,
|
|
3215
|
-
2,
|
|
3216
|
-
"off",
|
|
3217
|
-
"warn",
|
|
3218
|
-
"error"
|
|
3219
|
-
]
|
|
3220
|
-
},
|
|
3221
|
-
{
|
|
3222
|
-
type: "array",
|
|
3223
|
-
minItems: 1,
|
|
3224
|
-
maxItems: 1,
|
|
3225
|
-
items: [
|
|
3226
|
-
{
|
|
3227
|
-
"enum": [
|
|
3228
|
-
0,
|
|
3229
|
-
1,
|
|
3230
|
-
2,
|
|
3231
|
-
"off",
|
|
3232
|
-
"warn",
|
|
3233
|
-
"error"
|
|
3234
|
-
]
|
|
3235
|
-
}
|
|
3236
|
-
]
|
|
3237
|
-
},
|
|
3238
|
-
{
|
|
3239
|
-
type: "array",
|
|
3240
|
-
minItems: 2,
|
|
3241
|
-
maxItems: 2,
|
|
3242
|
-
items: [
|
|
3243
|
-
{
|
|
3244
|
-
"enum": [
|
|
3245
|
-
0,
|
|
3246
|
-
1,
|
|
3247
|
-
2,
|
|
3248
|
-
"off",
|
|
3249
|
-
"warn",
|
|
3250
|
-
"error"
|
|
3251
|
-
]
|
|
3252
|
-
},
|
|
3253
|
-
{
|
|
3254
|
-
}
|
|
3255
|
-
]
|
|
3256
|
-
}
|
|
3257
|
-
]
|
|
3258
|
-
}
|
|
3259
|
-
},
|
|
3260
|
-
title: "Rule configuration.",
|
|
3261
|
-
description: "Enable/disable rules, set severity. Some rules have additional configuration like style or patterns to use.",
|
|
3262
|
-
examples: [
|
|
3263
|
-
{
|
|
3264
|
-
foo: "error",
|
|
3265
|
-
bar: "off",
|
|
3266
|
-
baz: [
|
|
3267
|
-
"error",
|
|
3268
|
-
{
|
|
3269
|
-
style: "camelcase"
|
|
3270
|
-
}
|
|
3271
|
-
]
|
|
3272
|
-
}
|
|
3273
|
-
]
|
|
3274
|
-
}
|
|
3275
|
-
};
|
|
3276
|
-
var configurationSchema = {
|
|
3277
|
-
$schema: $schema,
|
|
3278
|
-
$id: $id,
|
|
3279
|
-
type: type,
|
|
3280
|
-
additionalProperties: additionalProperties,
|
|
3281
|
-
properties: properties
|
|
3282
|
-
};
|
|
3283
|
-
|
|
3284
|
-
const TRANSFORMER_API = {
|
|
3285
|
-
VERSION: 1
|
|
3286
|
-
};
|
|
3287
|
-
|
|
3288
|
-
var Severity = /* @__PURE__ */ ((Severity2) => {
|
|
3289
|
-
Severity2[Severity2["DISABLED"] = 0] = "DISABLED";
|
|
3290
|
-
Severity2[Severity2["WARN"] = 1] = "WARN";
|
|
3291
|
-
Severity2[Severity2["ERROR"] = 2] = "ERROR";
|
|
3292
|
-
return Severity2;
|
|
3293
|
-
})(Severity || {});
|
|
3294
|
-
function parseSeverity(value) {
|
|
3295
|
-
switch (value) {
|
|
3296
|
-
case 0:
|
|
3297
|
-
case "off":
|
|
3298
|
-
return 0 /* DISABLED */;
|
|
3299
|
-
case 1:
|
|
3300
|
-
case "warn":
|
|
3301
|
-
return 1 /* WARN */;
|
|
3302
|
-
case 2:
|
|
3303
|
-
case "error":
|
|
3304
|
-
return 2 /* ERROR */;
|
|
3305
|
-
default:
|
|
3306
|
-
throw new Error(`Invalid severity "${String(value)}"`);
|
|
3084
|
+
function isInertImpl(node) {
|
|
3085
|
+
const getInertAttr = (node2) => {
|
|
3086
|
+
const inert = node2.getAttribute("inert");
|
|
3087
|
+
return Boolean(inert == null ? void 0 : inert.isStatic);
|
|
3088
|
+
};
|
|
3089
|
+
return {
|
|
3090
|
+
byParent: node.parent ? isInert(node.parent) : false,
|
|
3091
|
+
bySelf: getInertAttr(node)
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
function isInert(node, details) {
|
|
3095
|
+
const cached = node.cacheGet(INERT_CACHE);
|
|
3096
|
+
if (cached) {
|
|
3097
|
+
return details ? cached : cached.byParent || cached.bySelf;
|
|
3307
3098
|
}
|
|
3099
|
+
const result = node.cacheSet(INERT_CACHE, isInertImpl(node));
|
|
3100
|
+
return details ? result : result.byParent || result.bySelf;
|
|
3308
3101
|
}
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3102
|
+
function isStyleHiddenImpl(node) {
|
|
3103
|
+
const getStyleAttr = (node2) => {
|
|
3104
|
+
const style = node2.getAttribute("style");
|
|
3105
|
+
const { display, visibility } = parseCssDeclaration(style == null ? void 0 : style.value);
|
|
3106
|
+
return display === "none" || visibility === "hidden";
|
|
3107
|
+
};
|
|
3108
|
+
const byParent = node.parent ? isStyleHidden(node.parent) : false;
|
|
3109
|
+
const bySelf = getStyleAttr(node);
|
|
3110
|
+
return byParent || bySelf;
|
|
3312
3111
|
}
|
|
3313
|
-
function
|
|
3314
|
-
|
|
3315
|
-
|
|
3112
|
+
function isStyleHidden(node) {
|
|
3113
|
+
const cached = node.cacheGet(STYLE_HIDDEN_CACHE);
|
|
3114
|
+
if (cached) {
|
|
3115
|
+
return cached;
|
|
3316
3116
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3117
|
+
return node.cacheSet(STYLE_HIDDEN_CACHE, isStyleHiddenImpl(node));
|
|
3118
|
+
}
|
|
3119
|
+
function isPresentation(node) {
|
|
3120
|
+
if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
|
|
3121
|
+
return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
|
|
3319
3122
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3123
|
+
const meta = node.meta;
|
|
3124
|
+
if (meta == null ? void 0 : meta.interactive) {
|
|
3125
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3322
3126
|
}
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
return
|
|
3127
|
+
const tabindex = node.getAttribute("tabindex");
|
|
3128
|
+
if (tabindex) {
|
|
3129
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3326
3130
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
return
|
|
3131
|
+
const role = node.getAttribute("role");
|
|
3132
|
+
if (role && (role.value === "presentation" || role.value === "none")) {
|
|
3133
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, true);
|
|
3134
|
+
} else {
|
|
3135
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3330
3136
|
}
|
|
3331
|
-
return String(value);
|
|
3332
|
-
}
|
|
3333
|
-
function interpolate(text, data) {
|
|
3334
|
-
return text.replace(/{{\s*([^\s{}]+)\s*}}/g, (match, key) => {
|
|
3335
|
-
return typeof data[key] !== "undefined" ? format(data[key]) : match;
|
|
3336
|
-
});
|
|
3337
3137
|
}
|
|
3338
3138
|
|
|
3339
|
-
const
|
|
3340
|
-
const
|
|
3341
|
-
const
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
"
|
|
3346
|
-
"
|
|
3347
|
-
"
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3139
|
+
const cachePrefix = classifyNodeText.name;
|
|
3140
|
+
const HTML_CACHE_KEY = Symbol(`${cachePrefix}|html`);
|
|
3141
|
+
const A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y`);
|
|
3142
|
+
const IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY = Symbol(`${cachePrefix}|html|ignore-hidden-root`);
|
|
3143
|
+
const IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y|ignore-hidden-root`);
|
|
3144
|
+
var TextClassification = /* @__PURE__ */ ((TextClassification2) => {
|
|
3145
|
+
TextClassification2[TextClassification2["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
3146
|
+
TextClassification2[TextClassification2["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
3147
|
+
TextClassification2[TextClassification2["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
3148
|
+
return TextClassification2;
|
|
3149
|
+
})(TextClassification || {});
|
|
3150
|
+
function getCachekey(options) {
|
|
3151
|
+
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
3152
|
+
if (accessible && ignoreHiddenRoot) {
|
|
3153
|
+
return IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY;
|
|
3154
|
+
} else if (ignoreHiddenRoot) {
|
|
3155
|
+
return IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY;
|
|
3156
|
+
} else if (accessible) {
|
|
3157
|
+
return A11Y_CACHE_KEY;
|
|
3158
|
+
} else {
|
|
3159
|
+
return HTML_CACHE_KEY;
|
|
3160
|
+
}
|
|
3356
3161
|
}
|
|
3357
|
-
function
|
|
3358
|
-
return
|
|
3359
|
-
}
|
|
3360
|
-
function ariaNaming(element) {
|
|
3361
|
-
var _a;
|
|
3362
|
-
const cached = element.cacheGet(cacheKey);
|
|
3363
|
-
if (cached) {
|
|
3364
|
-
return cached;
|
|
3365
|
-
}
|
|
3366
|
-
const role = (_a = element.getAttribute("role")) == null ? void 0 : _a.value;
|
|
3367
|
-
if (role) {
|
|
3368
|
-
if (role instanceof DynamicValue) {
|
|
3369
|
-
return element.cacheSet(cacheKey, defaultValue);
|
|
3370
|
-
} else {
|
|
3371
|
-
return element.cacheSet(cacheKey, byRole(role));
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
const meta = element.meta;
|
|
3375
|
-
if (!meta) {
|
|
3376
|
-
return element.cacheSet(cacheKey, defaultValue);
|
|
3377
|
-
}
|
|
3378
|
-
return element.cacheSet(cacheKey, byMeta(element, meta));
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
const patternCache = /* @__PURE__ */ new Map();
|
|
3382
|
-
function compileStringPattern(pattern) {
|
|
3383
|
-
const regexp = pattern.replace(/[*]+/g, ".+");
|
|
3384
|
-
return new RegExp(`^${regexp}$`);
|
|
3385
|
-
}
|
|
3386
|
-
function compileRegExpPattern(pattern) {
|
|
3387
|
-
return new RegExp(`^${pattern}$`);
|
|
3388
|
-
}
|
|
3389
|
-
function compilePattern(pattern) {
|
|
3390
|
-
const cached = patternCache.get(pattern);
|
|
3391
|
-
if (cached) {
|
|
3392
|
-
return cached;
|
|
3393
|
-
}
|
|
3394
|
-
const match = pattern.match(/^\/(.*)\/$/);
|
|
3395
|
-
const regexp = match ? compileRegExpPattern(match[1]) : compileStringPattern(pattern);
|
|
3396
|
-
patternCache.set(pattern, regexp);
|
|
3397
|
-
return regexp;
|
|
3398
|
-
}
|
|
3399
|
-
function keywordPatternMatcher(list, keyword) {
|
|
3400
|
-
for (const pattern of list) {
|
|
3401
|
-
const regexp = compilePattern(pattern);
|
|
3402
|
-
if (regexp.test(keyword)) {
|
|
3403
|
-
return true;
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
return false;
|
|
3407
|
-
}
|
|
3408
|
-
function isKeywordIgnored(options, keyword, matcher = (list, it) => list.includes(it)) {
|
|
3409
|
-
const { include, exclude } = options;
|
|
3410
|
-
if (include && !matcher(include, keyword)) {
|
|
3411
|
-
return true;
|
|
3412
|
-
}
|
|
3413
|
-
if (exclude && matcher(exclude, keyword)) {
|
|
3414
|
-
return true;
|
|
3415
|
-
}
|
|
3416
|
-
return false;
|
|
3417
|
-
}
|
|
3418
|
-
|
|
3419
|
-
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
3420
|
-
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
3421
|
-
const INERT_CACHE = Symbol(isInert.name);
|
|
3422
|
-
const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
|
|
3423
|
-
const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
|
|
3424
|
-
function inAccessibilityTree(node) {
|
|
3425
|
-
if (isAriaHidden(node)) {
|
|
3426
|
-
return false;
|
|
3427
|
-
}
|
|
3428
|
-
if (isPresentation(node)) {
|
|
3429
|
-
return false;
|
|
3430
|
-
}
|
|
3431
|
-
if (isHTMLHidden(node)) {
|
|
3432
|
-
return false;
|
|
3433
|
-
}
|
|
3434
|
-
if (isInert(node)) {
|
|
3435
|
-
return false;
|
|
3436
|
-
}
|
|
3437
|
-
if (isStyleHidden(node)) {
|
|
3438
|
-
return false;
|
|
3439
|
-
}
|
|
3440
|
-
return true;
|
|
3441
|
-
}
|
|
3442
|
-
function isAriaHiddenImpl(node) {
|
|
3443
|
-
const getAriaHiddenAttr = (node2) => {
|
|
3444
|
-
const ariaHidden = node2.getAttribute("aria-hidden");
|
|
3445
|
-
return Boolean(ariaHidden && ariaHidden.value === "true");
|
|
3446
|
-
};
|
|
3447
|
-
return {
|
|
3448
|
-
byParent: node.parent ? isAriaHidden(node.parent) : false,
|
|
3449
|
-
bySelf: getAriaHiddenAttr(node)
|
|
3450
|
-
};
|
|
3451
|
-
}
|
|
3452
|
-
function isAriaHidden(node, details) {
|
|
3453
|
-
const cached = node.cacheGet(ARIA_HIDDEN_CACHE);
|
|
3454
|
-
if (cached) {
|
|
3455
|
-
return details ? cached : cached.byParent || cached.bySelf;
|
|
3456
|
-
}
|
|
3457
|
-
const result = node.cacheSet(ARIA_HIDDEN_CACHE, isAriaHiddenImpl(node));
|
|
3458
|
-
return details ? result : result.byParent || result.bySelf;
|
|
3459
|
-
}
|
|
3460
|
-
function isHTMLHiddenImpl(node) {
|
|
3461
|
-
const getHiddenAttr = (node2) => {
|
|
3462
|
-
const hidden = node2.getAttribute("hidden");
|
|
3463
|
-
return Boolean(hidden == null ? void 0 : hidden.isStatic);
|
|
3464
|
-
};
|
|
3465
|
-
return {
|
|
3466
|
-
byParent: node.parent ? isHTMLHidden(node.parent) : false,
|
|
3467
|
-
bySelf: getHiddenAttr(node)
|
|
3468
|
-
};
|
|
3469
|
-
}
|
|
3470
|
-
function isHTMLHidden(node, details) {
|
|
3471
|
-
const cached = node.cacheGet(HTML_HIDDEN_CACHE);
|
|
3472
|
-
if (cached) {
|
|
3473
|
-
return details ? cached : cached.byParent || cached.bySelf;
|
|
3474
|
-
}
|
|
3475
|
-
const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
|
|
3476
|
-
return details ? result : result.byParent || result.bySelf;
|
|
3477
|
-
}
|
|
3478
|
-
function isInertImpl(node) {
|
|
3479
|
-
const getInertAttr = (node2) => {
|
|
3480
|
-
const inert = node2.getAttribute("inert");
|
|
3481
|
-
return Boolean(inert == null ? void 0 : inert.isStatic);
|
|
3482
|
-
};
|
|
3483
|
-
return {
|
|
3484
|
-
byParent: node.parent ? isInert(node.parent) : false,
|
|
3485
|
-
bySelf: getInertAttr(node)
|
|
3486
|
-
};
|
|
3487
|
-
}
|
|
3488
|
-
function isInert(node, details) {
|
|
3489
|
-
const cached = node.cacheGet(INERT_CACHE);
|
|
3490
|
-
if (cached) {
|
|
3491
|
-
return details ? cached : cached.byParent || cached.bySelf;
|
|
3492
|
-
}
|
|
3493
|
-
const result = node.cacheSet(INERT_CACHE, isInertImpl(node));
|
|
3494
|
-
return details ? result : result.byParent || result.bySelf;
|
|
3495
|
-
}
|
|
3496
|
-
function isStyleHiddenImpl(node) {
|
|
3497
|
-
const getStyleAttr = (node2) => {
|
|
3498
|
-
const style = node2.getAttribute("style");
|
|
3499
|
-
const { display, visibility } = parseCssDeclaration(style == null ? void 0 : style.value);
|
|
3500
|
-
return display === "none" || visibility === "hidden";
|
|
3501
|
-
};
|
|
3502
|
-
const byParent = node.parent ? isStyleHidden(node.parent) : false;
|
|
3503
|
-
const bySelf = getStyleAttr(node);
|
|
3504
|
-
return byParent || bySelf;
|
|
3505
|
-
}
|
|
3506
|
-
function isStyleHidden(node) {
|
|
3507
|
-
const cached = node.cacheGet(STYLE_HIDDEN_CACHE);
|
|
3508
|
-
if (cached) {
|
|
3509
|
-
return cached;
|
|
3510
|
-
}
|
|
3511
|
-
return node.cacheSet(STYLE_HIDDEN_CACHE, isStyleHiddenImpl(node));
|
|
3512
|
-
}
|
|
3513
|
-
function isPresentation(node) {
|
|
3514
|
-
if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
|
|
3515
|
-
return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
|
|
3516
|
-
}
|
|
3517
|
-
const meta = node.meta;
|
|
3518
|
-
if (meta == null ? void 0 : meta.interactive) {
|
|
3519
|
-
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3520
|
-
}
|
|
3521
|
-
const tabindex = node.getAttribute("tabindex");
|
|
3522
|
-
if (tabindex) {
|
|
3523
|
-
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3524
|
-
}
|
|
3525
|
-
const role = node.getAttribute("role");
|
|
3526
|
-
if (role && (role.value === "presentation" || role.value === "none")) {
|
|
3527
|
-
return node.cacheSet(ROLE_PRESENTATION_CACHE, true);
|
|
3528
|
-
} else {
|
|
3529
|
-
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
const cachePrefix = classifyNodeText.name;
|
|
3534
|
-
const HTML_CACHE_KEY = Symbol(`${cachePrefix}|html`);
|
|
3535
|
-
const A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y`);
|
|
3536
|
-
const IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY = Symbol(`${cachePrefix}|html|ignore-hidden-root`);
|
|
3537
|
-
const IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y|ignore-hidden-root`);
|
|
3538
|
-
var TextClassification = /* @__PURE__ */ ((TextClassification2) => {
|
|
3539
|
-
TextClassification2[TextClassification2["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
3540
|
-
TextClassification2[TextClassification2["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
3541
|
-
TextClassification2[TextClassification2["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
3542
|
-
return TextClassification2;
|
|
3543
|
-
})(TextClassification || {});
|
|
3544
|
-
function getCachekey(options) {
|
|
3545
|
-
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
3546
|
-
if (accessible && ignoreHiddenRoot) {
|
|
3547
|
-
return IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY;
|
|
3548
|
-
} else if (ignoreHiddenRoot) {
|
|
3549
|
-
return IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY;
|
|
3550
|
-
} else if (accessible) {
|
|
3551
|
-
return A11Y_CACHE_KEY;
|
|
3552
|
-
} else {
|
|
3553
|
-
return HTML_CACHE_KEY;
|
|
3554
|
-
}
|
|
3555
|
-
}
|
|
3556
|
-
function isSpecialEmpty(node) {
|
|
3557
|
-
return node.is("select") || node.is("textarea");
|
|
3162
|
+
function isSpecialEmpty(node) {
|
|
3163
|
+
return node.is("select") || node.is("textarea");
|
|
3558
3164
|
}
|
|
3559
3165
|
function classifyNodeText(node, options = {}) {
|
|
3560
3166
|
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
@@ -5012,7 +4618,7 @@ class AttributeAllowedValues extends Rule {
|
|
|
5012
4618
|
setup() {
|
|
5013
4619
|
this.on("dom:ready", (event) => {
|
|
5014
4620
|
const doc = event.document;
|
|
5015
|
-
|
|
4621
|
+
walk.depthFirst(doc, (node) => {
|
|
5016
4622
|
const meta = node.meta;
|
|
5017
4623
|
if (!(meta == null ? void 0 : meta.attributes))
|
|
5018
4624
|
return;
|
|
@@ -5072,7 +4678,7 @@ class AttributeBooleanStyle extends Rule {
|
|
|
5072
4678
|
setup() {
|
|
5073
4679
|
this.on("dom:ready", (event) => {
|
|
5074
4680
|
const doc = event.document;
|
|
5075
|
-
|
|
4681
|
+
walk.depthFirst(doc, (node) => {
|
|
5076
4682
|
const meta = node.meta;
|
|
5077
4683
|
if (!(meta == null ? void 0 : meta.attributes))
|
|
5078
4684
|
return;
|
|
@@ -5144,7 +4750,7 @@ class AttributeEmptyStyle extends Rule {
|
|
|
5144
4750
|
setup() {
|
|
5145
4751
|
this.on("dom:ready", (event) => {
|
|
5146
4752
|
const doc = event.document;
|
|
5147
|
-
|
|
4753
|
+
walk.depthFirst(doc, (node) => {
|
|
5148
4754
|
const meta = node.meta;
|
|
5149
4755
|
if (!(meta == null ? void 0 : meta.attributes))
|
|
5150
4756
|
return;
|
|
@@ -5796,7 +5402,7 @@ class ElementPermittedContent extends Rule {
|
|
|
5796
5402
|
setup() {
|
|
5797
5403
|
this.on("dom:ready", (event) => {
|
|
5798
5404
|
const doc = event.document;
|
|
5799
|
-
|
|
5405
|
+
walk.depthFirst(doc, (node) => {
|
|
5800
5406
|
const parent = node.parent;
|
|
5801
5407
|
if (!parent) {
|
|
5802
5408
|
return;
|
|
@@ -5875,7 +5481,7 @@ class ElementPermittedOccurrences extends Rule {
|
|
|
5875
5481
|
setup() {
|
|
5876
5482
|
this.on("dom:ready", (event) => {
|
|
5877
5483
|
const doc = event.document;
|
|
5878
|
-
|
|
5484
|
+
walk.depthFirst(doc, (node) => {
|
|
5879
5485
|
if (!node.meta) {
|
|
5880
5486
|
return;
|
|
5881
5487
|
}
|
|
@@ -5908,7 +5514,7 @@ class ElementPermittedOrder extends Rule {
|
|
|
5908
5514
|
setup() {
|
|
5909
5515
|
this.on("dom:ready", (event) => {
|
|
5910
5516
|
const doc = event.document;
|
|
5911
|
-
|
|
5517
|
+
walk.depthFirst(doc, (node) => {
|
|
5912
5518
|
if (!node.meta) {
|
|
5913
5519
|
return;
|
|
5914
5520
|
}
|
|
@@ -5978,7 +5584,7 @@ class ElementPermittedParent extends Rule {
|
|
|
5978
5584
|
setup() {
|
|
5979
5585
|
this.on("dom:ready", (event) => {
|
|
5980
5586
|
const doc = event.document;
|
|
5981
|
-
|
|
5587
|
+
walk.depthFirst(doc, (node) => {
|
|
5982
5588
|
var _a;
|
|
5983
5589
|
const parent = node.parent;
|
|
5984
5590
|
if (!parent) {
|
|
@@ -6026,7 +5632,7 @@ class ElementRequiredAncestor extends Rule {
|
|
|
6026
5632
|
setup() {
|
|
6027
5633
|
this.on("dom:ready", (event) => {
|
|
6028
5634
|
const doc = event.document;
|
|
6029
|
-
|
|
5635
|
+
walk.depthFirst(doc, (node) => {
|
|
6030
5636
|
const parent = node.parent;
|
|
6031
5637
|
if (!parent) {
|
|
6032
5638
|
return;
|
|
@@ -6110,7 +5716,7 @@ class ElementRequiredContent extends Rule {
|
|
|
6110
5716
|
setup() {
|
|
6111
5717
|
this.on("dom:ready", (event) => {
|
|
6112
5718
|
const doc = event.document;
|
|
6113
|
-
|
|
5719
|
+
walk.depthFirst(doc, (node) => {
|
|
6114
5720
|
if (!node.meta) {
|
|
6115
5721
|
return;
|
|
6116
5722
|
}
|
|
@@ -6630,7 +6236,7 @@ function isFocusableImpl(element) {
|
|
|
6630
6236
|
if (isDisabled(element, meta)) {
|
|
6631
6237
|
return false;
|
|
6632
6238
|
}
|
|
6633
|
-
return Boolean(meta
|
|
6239
|
+
return Boolean(meta.focusable);
|
|
6634
6240
|
}
|
|
6635
6241
|
function isFocusable(element) {
|
|
6636
6242
|
const cached = element.cacheGet(FOCUSABLE_CACHE);
|
|
@@ -9372,946 +8978,1459 @@ function matchFieldNames2(token) {
|
|
|
9372
8978
|
function matchWebauthn(token) {
|
|
9373
8979
|
return token === "webauthn";
|
|
9374
8980
|
}
|
|
9375
|
-
function matchToken(token) {
|
|
9376
|
-
if (matchSection(token)) {
|
|
9377
|
-
return "section";
|
|
8981
|
+
function matchToken(token) {
|
|
8982
|
+
if (matchSection(token)) {
|
|
8983
|
+
return "section";
|
|
8984
|
+
}
|
|
8985
|
+
if (matchHint(token)) {
|
|
8986
|
+
return "hint";
|
|
8987
|
+
}
|
|
8988
|
+
if (matchFieldNames1(token)) {
|
|
8989
|
+
return "field1";
|
|
8990
|
+
}
|
|
8991
|
+
if (matchFieldNames2(token)) {
|
|
8992
|
+
return "field2";
|
|
8993
|
+
}
|
|
8994
|
+
if (matchContact(token)) {
|
|
8995
|
+
return "contact";
|
|
8996
|
+
}
|
|
8997
|
+
if (matchWebauthn(token)) {
|
|
8998
|
+
return "webauthn";
|
|
8999
|
+
}
|
|
9000
|
+
return null;
|
|
9001
|
+
}
|
|
9002
|
+
function getControlGroups(type) {
|
|
9003
|
+
const allGroups = [
|
|
9004
|
+
"text",
|
|
9005
|
+
"multiline",
|
|
9006
|
+
"password",
|
|
9007
|
+
"url",
|
|
9008
|
+
"username",
|
|
9009
|
+
"tel",
|
|
9010
|
+
"numeric",
|
|
9011
|
+
"month",
|
|
9012
|
+
"date"
|
|
9013
|
+
];
|
|
9014
|
+
const mapping = {
|
|
9015
|
+
hidden: allGroups,
|
|
9016
|
+
text: allGroups.filter((it) => it !== "multiline"),
|
|
9017
|
+
search: allGroups.filter((it) => it !== "multiline"),
|
|
9018
|
+
password: ["password"],
|
|
9019
|
+
url: ["url"],
|
|
9020
|
+
email: ["username"],
|
|
9021
|
+
tel: ["tel"],
|
|
9022
|
+
number: ["numeric"],
|
|
9023
|
+
month: ["month"],
|
|
9024
|
+
date: ["date"]
|
|
9025
|
+
};
|
|
9026
|
+
return mapping[type] ?? [];
|
|
9027
|
+
}
|
|
9028
|
+
function isDisallowedType(node, type) {
|
|
9029
|
+
if (!node.is("input")) {
|
|
9030
|
+
return false;
|
|
9031
|
+
}
|
|
9032
|
+
return disallowedInputTypes.includes(type);
|
|
9033
|
+
}
|
|
9034
|
+
function getTerminalMessage(context) {
|
|
9035
|
+
switch (context.msg) {
|
|
9036
|
+
case 0 /* InvalidAttribute */:
|
|
9037
|
+
return "autocomplete attribute cannot be used on {{ what }}";
|
|
9038
|
+
case 1 /* InvalidValue */:
|
|
9039
|
+
return '"{{ value }}" cannot be used on {{ what }}';
|
|
9040
|
+
case 2 /* InvalidOrder */:
|
|
9041
|
+
return '"{{ second }}" must appear before "{{ first }}"';
|
|
9042
|
+
case 3 /* InvalidToken */:
|
|
9043
|
+
return '"{{ token }}" is not a valid autocomplete token or field name';
|
|
9044
|
+
case 4 /* InvalidCombination */:
|
|
9045
|
+
return '"{{ second }}" cannot be combined with "{{ first }}"';
|
|
9046
|
+
case 5 /* MissingField */:
|
|
9047
|
+
return "autocomplete attribute is missing field name";
|
|
9048
|
+
}
|
|
9049
|
+
}
|
|
9050
|
+
function getMarkdownMessage(context) {
|
|
9051
|
+
switch (context.msg) {
|
|
9052
|
+
case 0 /* InvalidAttribute */:
|
|
9053
|
+
return [
|
|
9054
|
+
`\`autocomplete\` attribute cannot be used on \`${context.what}\``,
|
|
9055
|
+
"",
|
|
9056
|
+
"The following input types cannot use the `autocomplete` attribute:",
|
|
9057
|
+
"",
|
|
9058
|
+
...disallowedInputTypes.map((it) => `- \`${it}\``)
|
|
9059
|
+
].join("\n");
|
|
9060
|
+
case 1 /* InvalidValue */: {
|
|
9061
|
+
const message = `\`"${context.value}"\` cannot be used on \`${context.what}\``;
|
|
9062
|
+
if (context.type === "form") {
|
|
9063
|
+
return [
|
|
9064
|
+
message,
|
|
9065
|
+
"",
|
|
9066
|
+
'The `<form>` element can only use the values `"on"` and `"off"`.'
|
|
9067
|
+
].join("\n");
|
|
9068
|
+
}
|
|
9069
|
+
if (context.type === "hidden") {
|
|
9070
|
+
return [
|
|
9071
|
+
message,
|
|
9072
|
+
"",
|
|
9073
|
+
'`<input type="hidden">` cannot use the values `"on"` and `"off"`.'
|
|
9074
|
+
].join("\n");
|
|
9075
|
+
}
|
|
9076
|
+
const controlGroups = getControlGroups(context.type);
|
|
9077
|
+
const currentGroup = fieldNameGroup[context.value];
|
|
9078
|
+
return [
|
|
9079
|
+
message,
|
|
9080
|
+
"",
|
|
9081
|
+
`\`${context.what}\` allows autocomplete fields from the following group${controlGroups.length > 1 ? "s" : ""}:`,
|
|
9082
|
+
"",
|
|
9083
|
+
...controlGroups.map((it) => `- ${it}`),
|
|
9084
|
+
"",
|
|
9085
|
+
`The field \`"${context.value}"\` belongs to the group /${currentGroup}/ which cannot be used with this input type.`
|
|
9086
|
+
].join("\n");
|
|
9087
|
+
}
|
|
9088
|
+
case 2 /* InvalidOrder */:
|
|
9089
|
+
return [
|
|
9090
|
+
`\`"${context.second}"\` must appear before \`"${context.first}"\``,
|
|
9091
|
+
"",
|
|
9092
|
+
"The autocomplete tokens must appear in the following order:",
|
|
9093
|
+
"",
|
|
9094
|
+
"- Optional section name (`section-` prefix).",
|
|
9095
|
+
"- Optional `shipping` or `billing` token.",
|
|
9096
|
+
"- Optional `home`, `work`, `mobile`, `fax` or `pager` token (for fields supporting it).",
|
|
9097
|
+
"- Field name",
|
|
9098
|
+
"- Optional `webauthn` token."
|
|
9099
|
+
].join("\n");
|
|
9100
|
+
case 3 /* InvalidToken */:
|
|
9101
|
+
return `\`"${context.token}"\` is not a valid autocomplete token or field name`;
|
|
9102
|
+
case 4 /* InvalidCombination */:
|
|
9103
|
+
return `\`"${context.second}"\` cannot be combined with \`"${context.first}"\``;
|
|
9104
|
+
case 5 /* MissingField */:
|
|
9105
|
+
return "Autocomplete attribute is missing field name";
|
|
9106
|
+
}
|
|
9107
|
+
}
|
|
9108
|
+
class ValidAutocomplete extends Rule {
|
|
9109
|
+
documentation(context) {
|
|
9110
|
+
return {
|
|
9111
|
+
description: getMarkdownMessage(context),
|
|
9112
|
+
url: "https://html-validate.org/rules/valid-autocomplete.html"
|
|
9113
|
+
};
|
|
9114
|
+
}
|
|
9115
|
+
setup() {
|
|
9116
|
+
this.on("dom:ready", (event) => {
|
|
9117
|
+
const { document } = event;
|
|
9118
|
+
const elements = document.querySelectorAll("[autocomplete]");
|
|
9119
|
+
for (const element of elements) {
|
|
9120
|
+
const autocomplete = element.getAttribute("autocomplete");
|
|
9121
|
+
if (autocomplete.value === null || autocomplete.value instanceof DynamicValue) {
|
|
9122
|
+
continue;
|
|
9123
|
+
}
|
|
9124
|
+
const location = autocomplete.valueLocation;
|
|
9125
|
+
const value = autocomplete.value.toLowerCase();
|
|
9126
|
+
const tokens = new DOMTokenList(value, location);
|
|
9127
|
+
if (tokens.length === 0) {
|
|
9128
|
+
continue;
|
|
9129
|
+
}
|
|
9130
|
+
this.validate(element, value, tokens, autocomplete.keyLocation, location);
|
|
9131
|
+
}
|
|
9132
|
+
});
|
|
9133
|
+
}
|
|
9134
|
+
validate(node, value, tokens, keyLocation, valueLocation) {
|
|
9135
|
+
switch (node.tagName) {
|
|
9136
|
+
case "form":
|
|
9137
|
+
this.validateFormAutocomplete(node, value, valueLocation);
|
|
9138
|
+
break;
|
|
9139
|
+
case "input":
|
|
9140
|
+
case "textarea":
|
|
9141
|
+
case "select":
|
|
9142
|
+
this.validateControlAutocomplete(node, tokens, keyLocation);
|
|
9143
|
+
break;
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
validateControlAutocomplete(node, tokens, keyLocation) {
|
|
9147
|
+
const type = node.getAttributeValue("type") ?? "text";
|
|
9148
|
+
const mantle = type !== "hidden" ? "expectation" : "anchor";
|
|
9149
|
+
if (isDisallowedType(node, type)) {
|
|
9150
|
+
const context = {
|
|
9151
|
+
msg: 0 /* InvalidAttribute */,
|
|
9152
|
+
what: `<input type="${type}">`
|
|
9153
|
+
};
|
|
9154
|
+
this.report({
|
|
9155
|
+
node,
|
|
9156
|
+
message: getTerminalMessage(context),
|
|
9157
|
+
location: keyLocation,
|
|
9158
|
+
context
|
|
9159
|
+
});
|
|
9160
|
+
return;
|
|
9161
|
+
}
|
|
9162
|
+
if (tokens.includes("on") || tokens.includes("off")) {
|
|
9163
|
+
this.validateOnOff(node, mantle, tokens);
|
|
9164
|
+
return;
|
|
9165
|
+
}
|
|
9166
|
+
this.validateTokens(node, tokens, keyLocation);
|
|
9167
|
+
}
|
|
9168
|
+
validateFormAutocomplete(node, value, location) {
|
|
9169
|
+
const trimmed = value.trim();
|
|
9170
|
+
if (["on", "off"].includes(trimmed)) {
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
const context = {
|
|
9174
|
+
msg: 1 /* InvalidValue */,
|
|
9175
|
+
type: "form",
|
|
9176
|
+
value: trimmed,
|
|
9177
|
+
what: "<form>"
|
|
9178
|
+
};
|
|
9179
|
+
this.report({
|
|
9180
|
+
node,
|
|
9181
|
+
message: getTerminalMessage(context),
|
|
9182
|
+
location,
|
|
9183
|
+
context
|
|
9184
|
+
});
|
|
9185
|
+
}
|
|
9186
|
+
validateOnOff(node, mantle, tokens) {
|
|
9187
|
+
const index = tokens.findIndex((it) => it === "on" || it === "off");
|
|
9188
|
+
const value = tokens.item(index);
|
|
9189
|
+
const location = tokens.location(index);
|
|
9190
|
+
if (tokens.length > 1) {
|
|
9191
|
+
const context = {
|
|
9192
|
+
msg: 4 /* InvalidCombination */,
|
|
9193
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9194
|
+
first: tokens.item(index > 0 ? 0 : 1),
|
|
9195
|
+
second: value
|
|
9196
|
+
};
|
|
9197
|
+
this.report({
|
|
9198
|
+
node,
|
|
9199
|
+
message: getTerminalMessage(context),
|
|
9200
|
+
location,
|
|
9201
|
+
context
|
|
9202
|
+
});
|
|
9203
|
+
}
|
|
9204
|
+
switch (mantle) {
|
|
9205
|
+
case "expectation":
|
|
9206
|
+
return;
|
|
9207
|
+
case "anchor": {
|
|
9208
|
+
const context = {
|
|
9209
|
+
msg: 1 /* InvalidValue */,
|
|
9210
|
+
type: "hidden",
|
|
9211
|
+
value,
|
|
9212
|
+
what: `<input type="hidden">`
|
|
9213
|
+
};
|
|
9214
|
+
this.report({
|
|
9215
|
+
node,
|
|
9216
|
+
message: getTerminalMessage(context),
|
|
9217
|
+
location: tokens.location(0),
|
|
9218
|
+
context
|
|
9219
|
+
});
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
}
|
|
9223
|
+
validateTokens(node, tokens, keyLocation) {
|
|
9224
|
+
const order = [];
|
|
9225
|
+
for (const { item, location } of tokens.iterator()) {
|
|
9226
|
+
const tokenType = matchToken(item);
|
|
9227
|
+
if (tokenType) {
|
|
9228
|
+
order.push(tokenType);
|
|
9229
|
+
} else {
|
|
9230
|
+
const context = {
|
|
9231
|
+
msg: 3 /* InvalidToken */,
|
|
9232
|
+
token: item
|
|
9233
|
+
};
|
|
9234
|
+
this.report({
|
|
9235
|
+
node,
|
|
9236
|
+
message: getTerminalMessage(context),
|
|
9237
|
+
location,
|
|
9238
|
+
context
|
|
9239
|
+
});
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9242
|
+
}
|
|
9243
|
+
const fieldTokens = order.map((it) => it === "field1" || it === "field2");
|
|
9244
|
+
this.validateFieldPresence(node, tokens, fieldTokens, keyLocation);
|
|
9245
|
+
this.validateContact(node, tokens, order);
|
|
9246
|
+
this.validateOrder(node, tokens, order);
|
|
9247
|
+
this.validateControlGroup(node, tokens, fieldTokens);
|
|
9248
|
+
}
|
|
9249
|
+
/**
|
|
9250
|
+
* Ensure that exactly one field name is present from the two field lists.
|
|
9251
|
+
*/
|
|
9252
|
+
validateFieldPresence(node, tokens, fieldTokens, keyLocation) {
|
|
9253
|
+
const numFields = fieldTokens.filter(Boolean).length;
|
|
9254
|
+
if (numFields === 0) {
|
|
9255
|
+
const context = {
|
|
9256
|
+
msg: 5 /* MissingField */
|
|
9257
|
+
};
|
|
9258
|
+
this.report({
|
|
9259
|
+
node,
|
|
9260
|
+
message: getTerminalMessage(context),
|
|
9261
|
+
location: keyLocation,
|
|
9262
|
+
context
|
|
9263
|
+
});
|
|
9264
|
+
} else if (numFields > 1) {
|
|
9265
|
+
const a = fieldTokens.indexOf(true);
|
|
9266
|
+
const b = fieldTokens.lastIndexOf(true);
|
|
9267
|
+
const context = {
|
|
9268
|
+
msg: 4 /* InvalidCombination */,
|
|
9269
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9270
|
+
first: tokens.item(a),
|
|
9271
|
+
second: tokens.item(b)
|
|
9272
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
9273
|
+
};
|
|
9274
|
+
this.report({
|
|
9275
|
+
node,
|
|
9276
|
+
message: getTerminalMessage(context),
|
|
9277
|
+
location: tokens.location(b),
|
|
9278
|
+
context
|
|
9279
|
+
});
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
/**
|
|
9283
|
+
* Ensure contact token is only used with field names from the second list.
|
|
9284
|
+
*/
|
|
9285
|
+
validateContact(node, tokens, order) {
|
|
9286
|
+
if (order.includes("contact") && order.includes("field1")) {
|
|
9287
|
+
const a = order.indexOf("field1");
|
|
9288
|
+
const b = order.indexOf("contact");
|
|
9289
|
+
const context = {
|
|
9290
|
+
msg: 4 /* InvalidCombination */,
|
|
9291
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9292
|
+
first: tokens.item(a),
|
|
9293
|
+
second: tokens.item(b)
|
|
9294
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
9295
|
+
};
|
|
9296
|
+
this.report({
|
|
9297
|
+
node,
|
|
9298
|
+
message: getTerminalMessage(context),
|
|
9299
|
+
location: tokens.location(b),
|
|
9300
|
+
context
|
|
9301
|
+
});
|
|
9302
|
+
}
|
|
9303
|
+
}
|
|
9304
|
+
validateOrder(node, tokens, order) {
|
|
9305
|
+
const indicies = order.map((it) => expectedOrder.indexOf(it));
|
|
9306
|
+
for (let i = 0; i < indicies.length - 1; i++) {
|
|
9307
|
+
if (indicies[0] > indicies[i + 1]) {
|
|
9308
|
+
const context = {
|
|
9309
|
+
msg: 2 /* InvalidOrder */,
|
|
9310
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9311
|
+
first: tokens.item(i),
|
|
9312
|
+
second: tokens.item(i + 1)
|
|
9313
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
9314
|
+
};
|
|
9315
|
+
this.report({
|
|
9316
|
+
node,
|
|
9317
|
+
message: getTerminalMessage(context),
|
|
9318
|
+
location: tokens.location(i + 1),
|
|
9319
|
+
context
|
|
9320
|
+
});
|
|
9321
|
+
}
|
|
9322
|
+
}
|
|
9323
|
+
}
|
|
9324
|
+
validateControlGroup(node, tokens, fieldTokens) {
|
|
9325
|
+
const numFields = fieldTokens.filter(Boolean).length;
|
|
9326
|
+
if (numFields === 0) {
|
|
9327
|
+
return;
|
|
9328
|
+
}
|
|
9329
|
+
if (!node.is("input")) {
|
|
9330
|
+
return;
|
|
9331
|
+
}
|
|
9332
|
+
const attr = node.getAttribute("type");
|
|
9333
|
+
const type = (attr == null ? void 0 : attr.value) ?? "text";
|
|
9334
|
+
if (type instanceof DynamicValue) {
|
|
9335
|
+
return;
|
|
9336
|
+
}
|
|
9337
|
+
const controlGroups = getControlGroups(type);
|
|
9338
|
+
const fieldIndex = fieldTokens.indexOf(true);
|
|
9339
|
+
const fieldToken = tokens.item(fieldIndex);
|
|
9340
|
+
const fieldGroup = fieldNameGroup[fieldToken];
|
|
9341
|
+
if (!controlGroups.includes(fieldGroup)) {
|
|
9342
|
+
const context = {
|
|
9343
|
+
msg: 1 /* InvalidValue */,
|
|
9344
|
+
type,
|
|
9345
|
+
value: fieldToken,
|
|
9346
|
+
what: `<input type="${type}">`
|
|
9347
|
+
};
|
|
9348
|
+
this.report({
|
|
9349
|
+
node,
|
|
9350
|
+
message: getTerminalMessage(context),
|
|
9351
|
+
location: tokens.location(fieldIndex),
|
|
9352
|
+
context
|
|
9353
|
+
});
|
|
9354
|
+
}
|
|
9355
|
+
}
|
|
9356
|
+
}
|
|
9357
|
+
|
|
9358
|
+
const defaults$3 = {
|
|
9359
|
+
relaxed: false
|
|
9360
|
+
};
|
|
9361
|
+
class ValidID extends Rule {
|
|
9362
|
+
constructor(options) {
|
|
9363
|
+
super({ ...defaults$3, ...options });
|
|
9378
9364
|
}
|
|
9379
|
-
|
|
9380
|
-
return
|
|
9365
|
+
static schema() {
|
|
9366
|
+
return {
|
|
9367
|
+
relaxed: {
|
|
9368
|
+
type: "boolean"
|
|
9369
|
+
}
|
|
9370
|
+
};
|
|
9381
9371
|
}
|
|
9382
|
-
|
|
9383
|
-
|
|
9372
|
+
documentation(context) {
|
|
9373
|
+
const { relaxed } = this.options;
|
|
9374
|
+
const message = this.messages[context].replace("id", "ID").replace(/^(.)/, (m) => m.toUpperCase());
|
|
9375
|
+
const relaxedDescription = relaxed ? [] : [
|
|
9376
|
+
" - ID must begin with a letter",
|
|
9377
|
+
" - ID must only contain letters, digits, `-` and `_`"
|
|
9378
|
+
];
|
|
9379
|
+
return {
|
|
9380
|
+
description: [
|
|
9381
|
+
`${message}.`,
|
|
9382
|
+
"",
|
|
9383
|
+
"Under the current configuration the following rules are applied:",
|
|
9384
|
+
"",
|
|
9385
|
+
" - ID must not be empty",
|
|
9386
|
+
" - ID must not contain any whitespace characters",
|
|
9387
|
+
...relaxedDescription
|
|
9388
|
+
].join("\n"),
|
|
9389
|
+
url: "https://html-validate.org/rules/valid-id.html"
|
|
9390
|
+
};
|
|
9384
9391
|
}
|
|
9385
|
-
|
|
9386
|
-
|
|
9392
|
+
setup() {
|
|
9393
|
+
this.on("attr", this.isRelevant, (event) => {
|
|
9394
|
+
const { value } = event;
|
|
9395
|
+
if (value === null || value instanceof DynamicValue) {
|
|
9396
|
+
return;
|
|
9397
|
+
}
|
|
9398
|
+
if (value === "") {
|
|
9399
|
+
const context = 1 /* EMPTY */;
|
|
9400
|
+
this.report(event.target, this.messages[context], event.location, context);
|
|
9401
|
+
return;
|
|
9402
|
+
}
|
|
9403
|
+
if (value.match(/\s/)) {
|
|
9404
|
+
const context = 2 /* WHITESPACE */;
|
|
9405
|
+
this.report(event.target, this.messages[context], event.valueLocation, context);
|
|
9406
|
+
return;
|
|
9407
|
+
}
|
|
9408
|
+
const { relaxed } = this.options;
|
|
9409
|
+
if (relaxed) {
|
|
9410
|
+
return;
|
|
9411
|
+
}
|
|
9412
|
+
if (value.match(/^[^\p{L}]/u)) {
|
|
9413
|
+
const context = 3 /* LEADING_CHARACTER */;
|
|
9414
|
+
this.report(event.target, this.messages[context], event.valueLocation, context);
|
|
9415
|
+
return;
|
|
9416
|
+
}
|
|
9417
|
+
if (value.match(/[^\p{L}\p{N}_-]/u)) {
|
|
9418
|
+
const context = 4 /* DISALLOWED_CHARACTER */;
|
|
9419
|
+
this.report(event.target, this.messages[context], event.valueLocation, context);
|
|
9420
|
+
}
|
|
9421
|
+
});
|
|
9387
9422
|
}
|
|
9388
|
-
|
|
9389
|
-
return
|
|
9423
|
+
get messages() {
|
|
9424
|
+
return {
|
|
9425
|
+
[1 /* EMPTY */]: "element id must not be empty",
|
|
9426
|
+
[2 /* WHITESPACE */]: "element id must not contain whitespace",
|
|
9427
|
+
[3 /* LEADING_CHARACTER */]: "element id must begin with a letter",
|
|
9428
|
+
[4 /* DISALLOWED_CHARACTER */]: "element id must only contain letters, digits, dash and underscore characters"
|
|
9429
|
+
};
|
|
9390
9430
|
}
|
|
9391
|
-
|
|
9392
|
-
return "
|
|
9431
|
+
isRelevant(event) {
|
|
9432
|
+
return event.key === "id";
|
|
9393
9433
|
}
|
|
9394
|
-
return null;
|
|
9395
9434
|
}
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
];
|
|
9408
|
-
const mapping = {
|
|
9409
|
-
hidden: allGroups,
|
|
9410
|
-
text: allGroups.filter((it) => it !== "multiline"),
|
|
9411
|
-
search: allGroups.filter((it) => it !== "multiline"),
|
|
9412
|
-
password: ["password"],
|
|
9413
|
-
url: ["url"],
|
|
9414
|
-
email: ["username"],
|
|
9415
|
-
tel: ["tel"],
|
|
9416
|
-
number: ["numeric"],
|
|
9417
|
-
month: ["month"],
|
|
9418
|
-
date: ["date"]
|
|
9419
|
-
};
|
|
9420
|
-
const groups = mapping[type];
|
|
9421
|
-
if (groups) {
|
|
9422
|
-
return groups;
|
|
9435
|
+
|
|
9436
|
+
class VoidContent extends Rule {
|
|
9437
|
+
documentation(tagName) {
|
|
9438
|
+
const doc = {
|
|
9439
|
+
description: "HTML void elements cannot have any content and must not have content or end tag.",
|
|
9440
|
+
url: "https://html-validate.org/rules/void-content.html"
|
|
9441
|
+
};
|
|
9442
|
+
if (tagName) {
|
|
9443
|
+
doc.description = `<${tagName}> is a void element and must not have content or end tag.`;
|
|
9444
|
+
}
|
|
9445
|
+
return doc;
|
|
9423
9446
|
}
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9447
|
+
setup() {
|
|
9448
|
+
this.on("tag:end", (event) => {
|
|
9449
|
+
const node = event.target;
|
|
9450
|
+
if (!node) {
|
|
9451
|
+
return;
|
|
9452
|
+
}
|
|
9453
|
+
if (!node.voidElement) {
|
|
9454
|
+
return;
|
|
9455
|
+
}
|
|
9456
|
+
if (node.closed === NodeClosed.EndTag) {
|
|
9457
|
+
this.report(
|
|
9458
|
+
null,
|
|
9459
|
+
`End tag for <${node.tagName}> must be omitted`,
|
|
9460
|
+
node.location,
|
|
9461
|
+
node.tagName
|
|
9462
|
+
);
|
|
9463
|
+
}
|
|
9464
|
+
});
|
|
9429
9465
|
}
|
|
9430
|
-
return disallowedInputTypes.includes(type);
|
|
9431
9466
|
}
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
case 3 /* InvalidToken */:
|
|
9441
|
-
return '"{{ token }}" is not a valid autocomplete token or field name';
|
|
9442
|
-
case 4 /* InvalidCombination */:
|
|
9443
|
-
return '"{{ second }}" cannot be combined with "{{ first }}"';
|
|
9444
|
-
case 5 /* MissingField */:
|
|
9445
|
-
return "autocomplete attribute is missing field name";
|
|
9467
|
+
|
|
9468
|
+
const defaults$2 = {
|
|
9469
|
+
style: "omit"
|
|
9470
|
+
};
|
|
9471
|
+
class VoidStyle extends Rule {
|
|
9472
|
+
constructor(options) {
|
|
9473
|
+
super({ ...defaults$2, ...options });
|
|
9474
|
+
this.style = parseStyle(this.options.style);
|
|
9446
9475
|
}
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
"The following input types cannot use the `autocomplete` attribute:",
|
|
9455
|
-
"",
|
|
9456
|
-
...disallowedInputTypes.map((it) => `- \`${it}\``)
|
|
9457
|
-
].join("\n");
|
|
9458
|
-
case 1 /* InvalidValue */: {
|
|
9459
|
-
const message = `\`"${context.value}"\` cannot be used on \`${context.what}\``;
|
|
9460
|
-
if (context.type === "form") {
|
|
9461
|
-
return [
|
|
9462
|
-
message,
|
|
9463
|
-
"",
|
|
9464
|
-
'The `<form>` element can only use the values `"on"` and `"off"`.'
|
|
9465
|
-
].join("\n");
|
|
9466
|
-
}
|
|
9467
|
-
if (context.type === "hidden") {
|
|
9468
|
-
return [
|
|
9469
|
-
message,
|
|
9470
|
-
"",
|
|
9471
|
-
'`<input type="hidden">` cannot use the values `"on"` and `"off"`.'
|
|
9472
|
-
].join("\n");
|
|
9473
|
-
}
|
|
9474
|
-
const controlGroups = getControlGroups(context.type);
|
|
9475
|
-
const currentGroup = fieldNameGroup[context.value];
|
|
9476
|
-
return [
|
|
9477
|
-
message,
|
|
9478
|
-
"",
|
|
9479
|
-
`\`${context.what}\` allows autocomplete fields from the following group${controlGroups.length > 1 ? "s" : ""}:`,
|
|
9480
|
-
"",
|
|
9481
|
-
...controlGroups.map((it) => `- ${it}`),
|
|
9482
|
-
"",
|
|
9483
|
-
`The field \`"${context.value}"\` belongs to the group /${currentGroup}/ which cannot be used with this input type.`
|
|
9484
|
-
].join("\n");
|
|
9485
|
-
}
|
|
9486
|
-
case 2 /* InvalidOrder */:
|
|
9487
|
-
return [
|
|
9488
|
-
`\`"${context.second}"\` must appear before \`"${context.first}"\``,
|
|
9489
|
-
"",
|
|
9490
|
-
"The autocomplete tokens must appear in the following order:",
|
|
9491
|
-
"",
|
|
9492
|
-
"- Optional section name (`section-` prefix).",
|
|
9493
|
-
"- Optional `shipping` or `billing` token.",
|
|
9494
|
-
"- Optional `home`, `work`, `mobile`, `fax` or `pager` token (for fields supporting it).",
|
|
9495
|
-
"- Field name",
|
|
9496
|
-
"- Optional `webauthn` token."
|
|
9497
|
-
].join("\n");
|
|
9498
|
-
case 3 /* InvalidToken */:
|
|
9499
|
-
return `\`"${context.token}"\` is not a valid autocomplete token or field name`;
|
|
9500
|
-
case 4 /* InvalidCombination */:
|
|
9501
|
-
return `\`"${context.second}"\` cannot be combined with \`"${context.first}"\``;
|
|
9502
|
-
case 5 /* MissingField */:
|
|
9503
|
-
return "Autocomplete attribute is missing field name";
|
|
9476
|
+
static schema() {
|
|
9477
|
+
return {
|
|
9478
|
+
style: {
|
|
9479
|
+
enum: ["omit", "selfclose", "selfclosing"],
|
|
9480
|
+
type: "string"
|
|
9481
|
+
}
|
|
9482
|
+
};
|
|
9504
9483
|
}
|
|
9505
|
-
}
|
|
9506
|
-
class ValidAutocomplete extends Rule {
|
|
9507
9484
|
documentation(context) {
|
|
9485
|
+
const [desc, end] = styleDescription(context.style);
|
|
9508
9486
|
return {
|
|
9509
|
-
description:
|
|
9510
|
-
url: "https://html-validate.org/rules/
|
|
9487
|
+
description: `The current configuration requires void elements to ${desc}, use <${context.tagName}${end}> instead.`,
|
|
9488
|
+
url: "https://html-validate.org/rules/void-style.html"
|
|
9511
9489
|
};
|
|
9512
9490
|
}
|
|
9513
9491
|
setup() {
|
|
9514
|
-
this.on("
|
|
9515
|
-
const
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
const autocomplete = element.getAttribute("autocomplete");
|
|
9519
|
-
if (autocomplete.value === null || autocomplete.value instanceof DynamicValue) {
|
|
9520
|
-
continue;
|
|
9521
|
-
}
|
|
9522
|
-
const location = autocomplete.valueLocation;
|
|
9523
|
-
const value = autocomplete.value.toLowerCase();
|
|
9524
|
-
const tokens = new DOMTokenList(value, location);
|
|
9525
|
-
if (tokens.length === 0) {
|
|
9526
|
-
continue;
|
|
9527
|
-
}
|
|
9528
|
-
this.validate(element, value, tokens, autocomplete.keyLocation, location);
|
|
9492
|
+
this.on("tag:end", (event) => {
|
|
9493
|
+
const active = event.previous;
|
|
9494
|
+
if (active.meta) {
|
|
9495
|
+
this.validateActive(active);
|
|
9529
9496
|
}
|
|
9530
9497
|
});
|
|
9531
9498
|
}
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
this.validateFormAutocomplete(node, value, valueLocation);
|
|
9536
|
-
break;
|
|
9537
|
-
case "input":
|
|
9538
|
-
case "textarea":
|
|
9539
|
-
case "select":
|
|
9540
|
-
this.validateControlAutocomplete(node, tokens, keyLocation);
|
|
9541
|
-
break;
|
|
9499
|
+
validateActive(node) {
|
|
9500
|
+
if (!node.voidElement) {
|
|
9501
|
+
return;
|
|
9542
9502
|
}
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
const type = node.getAttributeValue("type") ?? "text";
|
|
9546
|
-
const mantle = type !== "hidden" ? "expectation" : "anchor";
|
|
9547
|
-
if (isDisallowedType(node, type)) {
|
|
9548
|
-
const context = {
|
|
9549
|
-
msg: 0 /* InvalidAttribute */,
|
|
9550
|
-
what: `<input type="${type}">`
|
|
9551
|
-
};
|
|
9552
|
-
this.report({
|
|
9503
|
+
if (this.shouldBeOmitted(node)) {
|
|
9504
|
+
this.reportError(
|
|
9553
9505
|
node,
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
context
|
|
9557
|
-
});
|
|
9558
|
-
return;
|
|
9506
|
+
`Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`
|
|
9507
|
+
);
|
|
9559
9508
|
}
|
|
9560
|
-
if (
|
|
9561
|
-
this.
|
|
9562
|
-
|
|
9509
|
+
if (this.shouldBeSelfClosed(node)) {
|
|
9510
|
+
this.reportError(
|
|
9511
|
+
node,
|
|
9512
|
+
`Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`
|
|
9513
|
+
);
|
|
9563
9514
|
}
|
|
9564
|
-
this.validateTokens(node, tokens, keyLocation);
|
|
9565
9515
|
}
|
|
9566
|
-
|
|
9567
|
-
const trimmed = value.trim();
|
|
9568
|
-
if (["on", "off"].includes(trimmed)) {
|
|
9569
|
-
return;
|
|
9570
|
-
}
|
|
9516
|
+
reportError(node, message) {
|
|
9571
9517
|
const context = {
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
value: trimmed,
|
|
9575
|
-
what: "<form>"
|
|
9518
|
+
style: this.style,
|
|
9519
|
+
tagName: node.tagName
|
|
9576
9520
|
};
|
|
9577
|
-
|
|
9578
|
-
node,
|
|
9579
|
-
message: getTerminalMessage(context),
|
|
9580
|
-
location,
|
|
9581
|
-
context
|
|
9582
|
-
});
|
|
9521
|
+
super.report(node, message, null, context);
|
|
9583
9522
|
}
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
const value = tokens.item(index);
|
|
9587
|
-
const location = tokens.location(index);
|
|
9588
|
-
if (tokens.length > 1) {
|
|
9589
|
-
const context = {
|
|
9590
|
-
msg: 4 /* InvalidCombination */,
|
|
9591
|
-
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9592
|
-
first: tokens.item(index > 0 ? 0 : 1),
|
|
9593
|
-
second: value
|
|
9594
|
-
};
|
|
9595
|
-
this.report({
|
|
9596
|
-
node,
|
|
9597
|
-
message: getTerminalMessage(context),
|
|
9598
|
-
location,
|
|
9599
|
-
context
|
|
9600
|
-
});
|
|
9601
|
-
}
|
|
9602
|
-
switch (mantle) {
|
|
9603
|
-
case "expectation":
|
|
9604
|
-
return;
|
|
9605
|
-
case "anchor": {
|
|
9606
|
-
const context = {
|
|
9607
|
-
msg: 1 /* InvalidValue */,
|
|
9608
|
-
type: "hidden",
|
|
9609
|
-
value,
|
|
9610
|
-
what: `<input type="hidden">`
|
|
9611
|
-
};
|
|
9612
|
-
this.report({
|
|
9613
|
-
node,
|
|
9614
|
-
message: getTerminalMessage(context),
|
|
9615
|
-
location: tokens.location(0),
|
|
9616
|
-
context
|
|
9617
|
-
});
|
|
9618
|
-
}
|
|
9619
|
-
}
|
|
9523
|
+
shouldBeOmitted(node) {
|
|
9524
|
+
return this.style === 1 /* AlwaysOmit */ && node.closed === NodeClosed.VoidSelfClosed;
|
|
9620
9525
|
}
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9526
|
+
shouldBeSelfClosed(node) {
|
|
9527
|
+
return this.style === 2 /* AlwaysSelfclose */ && node.closed === NodeClosed.VoidOmitted;
|
|
9528
|
+
}
|
|
9529
|
+
}
|
|
9530
|
+
function parseStyle(name) {
|
|
9531
|
+
switch (name) {
|
|
9532
|
+
case "omit":
|
|
9533
|
+
return 1 /* AlwaysOmit */;
|
|
9534
|
+
case "selfclose":
|
|
9535
|
+
case "selfclosing":
|
|
9536
|
+
return 2 /* AlwaysSelfclose */;
|
|
9537
|
+
default:
|
|
9538
|
+
throw new Error(`Invalid style "${name}" for "void-style" rule`);
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
function styleDescription(style) {
|
|
9542
|
+
switch (style) {
|
|
9543
|
+
case 1 /* AlwaysOmit */:
|
|
9544
|
+
return ["omit end tag", ""];
|
|
9545
|
+
case 2 /* AlwaysSelfclose */:
|
|
9546
|
+
return ["be self-closed", "/"];
|
|
9547
|
+
default:
|
|
9548
|
+
throw new Error(`Unknown style`);
|
|
9549
|
+
}
|
|
9550
|
+
}
|
|
9551
|
+
|
|
9552
|
+
class H30 extends Rule {
|
|
9553
|
+
documentation() {
|
|
9554
|
+
return {
|
|
9555
|
+
description: "WCAG 2.1 requires each `<a href>` anchor link to have a text describing the purpose of the link using either plain text or an `<img>` with the `alt` attribute set.",
|
|
9556
|
+
url: "https://html-validate.org/rules/wcag/h30.html"
|
|
9557
|
+
};
|
|
9558
|
+
}
|
|
9559
|
+
setup() {
|
|
9560
|
+
this.on("dom:ready", (event) => {
|
|
9561
|
+
const links = event.document.getElementsByTagName("a");
|
|
9562
|
+
for (const link of links) {
|
|
9563
|
+
if (!link.hasAttribute("href")) {
|
|
9564
|
+
continue;
|
|
9565
|
+
}
|
|
9566
|
+
if (!inAccessibilityTree(link)) {
|
|
9567
|
+
continue;
|
|
9568
|
+
}
|
|
9569
|
+
const textClassification = classifyNodeText(link, { ignoreHiddenRoot: true });
|
|
9570
|
+
if (textClassification !== TextClassification.EMPTY_TEXT) {
|
|
9571
|
+
continue;
|
|
9572
|
+
}
|
|
9573
|
+
const images = link.querySelectorAll("img");
|
|
9574
|
+
if (images.some((image) => hasAltText(image))) {
|
|
9575
|
+
continue;
|
|
9576
|
+
}
|
|
9577
|
+
const labels = link.querySelectorAll("[aria-label]");
|
|
9578
|
+
if (hasAriaLabel(link) || labels.some((cur) => hasAriaLabel(cur))) {
|
|
9579
|
+
continue;
|
|
9580
|
+
}
|
|
9581
|
+
this.report(link, "Anchor link must have a text describing its purpose");
|
|
9639
9582
|
}
|
|
9640
|
-
}
|
|
9641
|
-
const fieldTokens = order.map((it) => it === "field1" || it === "field2");
|
|
9642
|
-
this.validateFieldPresence(node, tokens, fieldTokens, keyLocation);
|
|
9643
|
-
this.validateContact(node, tokens, order);
|
|
9644
|
-
this.validateOrder(node, tokens, order);
|
|
9645
|
-
this.validateControlGroup(node, tokens, fieldTokens);
|
|
9583
|
+
});
|
|
9646
9584
|
}
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
this.report({
|
|
9673
|
-
node,
|
|
9674
|
-
message: getTerminalMessage(context),
|
|
9675
|
-
location: tokens.location(b),
|
|
9676
|
-
context
|
|
9677
|
-
});
|
|
9678
|
-
}
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9587
|
+
class H32 extends Rule {
|
|
9588
|
+
documentation() {
|
|
9589
|
+
return {
|
|
9590
|
+
description: "WCAG 2.1 requires each `<form>` element to have at least one submit button.",
|
|
9591
|
+
url: "https://html-validate.org/rules/wcag/h32.html"
|
|
9592
|
+
};
|
|
9593
|
+
}
|
|
9594
|
+
setup() {
|
|
9595
|
+
const formTags = this.getTagsWithProperty("form");
|
|
9596
|
+
const formSelector = formTags.join(",");
|
|
9597
|
+
this.on("dom:ready", (event) => {
|
|
9598
|
+
const { document } = event;
|
|
9599
|
+
const forms = document.querySelectorAll(formSelector);
|
|
9600
|
+
for (const form of forms) {
|
|
9601
|
+
if (hasNestedSubmit(form)) {
|
|
9602
|
+
continue;
|
|
9603
|
+
}
|
|
9604
|
+
if (hasAssociatedSubmit(document, form)) {
|
|
9605
|
+
continue;
|
|
9606
|
+
}
|
|
9607
|
+
this.report(form, `<${form.tagName}> element must have a submit button`);
|
|
9608
|
+
}
|
|
9609
|
+
});
|
|
9679
9610
|
}
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
location: tokens.location(b),
|
|
9698
|
-
context
|
|
9699
|
-
});
|
|
9700
|
-
}
|
|
9611
|
+
}
|
|
9612
|
+
function isSubmit(node) {
|
|
9613
|
+
const type = node.getAttribute("type");
|
|
9614
|
+
return Boolean(!type || type.valueMatches(/submit|image/));
|
|
9615
|
+
}
|
|
9616
|
+
function isAssociated(id, node) {
|
|
9617
|
+
const form = node.getAttribute("form");
|
|
9618
|
+
return Boolean(form == null ? void 0 : form.valueMatches(id, true));
|
|
9619
|
+
}
|
|
9620
|
+
function hasNestedSubmit(form) {
|
|
9621
|
+
const matches = form.querySelectorAll("button,input").filter(isSubmit).filter((node) => !node.hasAttribute("form"));
|
|
9622
|
+
return matches.length > 0;
|
|
9623
|
+
}
|
|
9624
|
+
function hasAssociatedSubmit(document, form) {
|
|
9625
|
+
const { id } = form;
|
|
9626
|
+
if (!id) {
|
|
9627
|
+
return false;
|
|
9701
9628
|
}
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9629
|
+
const matches = document.querySelectorAll("button[form],input[form]").filter(isSubmit).filter((node) => isAssociated(id, node));
|
|
9630
|
+
return matches.length > 0;
|
|
9631
|
+
}
|
|
9632
|
+
|
|
9633
|
+
class H36 extends Rule {
|
|
9634
|
+
documentation() {
|
|
9635
|
+
return {
|
|
9636
|
+
description: [
|
|
9637
|
+
"WCAG 2.1 requires all images used as submit buttons to have a non-empty textual description using the `alt` attribute.",
|
|
9638
|
+
'The alt text cannot be empty (`alt=""`).'
|
|
9639
|
+
].join("\n"),
|
|
9640
|
+
url: "https://html-validate.org/rules/wcag/h36.html"
|
|
9641
|
+
};
|
|
9642
|
+
}
|
|
9643
|
+
setup() {
|
|
9644
|
+
this.on("tag:end", (event) => {
|
|
9645
|
+
const node = event.previous;
|
|
9646
|
+
if (node.tagName !== "input")
|
|
9647
|
+
return;
|
|
9648
|
+
if (node.getAttributeValue("type") !== "image") {
|
|
9649
|
+
return;
|
|
9650
|
+
}
|
|
9651
|
+
if (!inAccessibilityTree(node)) {
|
|
9652
|
+
return;
|
|
9653
|
+
}
|
|
9654
|
+
if (!hasAltText(node)) {
|
|
9655
|
+
const message = "image used as submit button must have non-empty alt text";
|
|
9656
|
+
const alt = node.getAttribute("alt");
|
|
9713
9657
|
this.report({
|
|
9714
9658
|
node,
|
|
9715
|
-
message
|
|
9716
|
-
location:
|
|
9717
|
-
context
|
|
9659
|
+
message,
|
|
9660
|
+
location: alt ? alt.keyLocation : node.location
|
|
9718
9661
|
});
|
|
9719
9662
|
}
|
|
9720
|
-
}
|
|
9721
|
-
}
|
|
9722
|
-
validateControlGroup(node, tokens, fieldTokens) {
|
|
9723
|
-
const numFields = fieldTokens.filter(Boolean).length;
|
|
9724
|
-
if (numFields === 0) {
|
|
9725
|
-
return;
|
|
9726
|
-
}
|
|
9727
|
-
if (!node.is("input")) {
|
|
9728
|
-
return;
|
|
9729
|
-
}
|
|
9730
|
-
const attr = node.getAttribute("type");
|
|
9731
|
-
const type = (attr == null ? void 0 : attr.value) ?? "text";
|
|
9732
|
-
if (type instanceof DynamicValue) {
|
|
9733
|
-
return;
|
|
9734
|
-
}
|
|
9735
|
-
const controlGroups = getControlGroups(type);
|
|
9736
|
-
const fieldIndex = fieldTokens.indexOf(true);
|
|
9737
|
-
const fieldToken = tokens.item(fieldIndex);
|
|
9738
|
-
const fieldGroup = fieldNameGroup[fieldToken];
|
|
9739
|
-
if (!controlGroups.includes(fieldGroup)) {
|
|
9740
|
-
const context = {
|
|
9741
|
-
msg: 1 /* InvalidValue */,
|
|
9742
|
-
type,
|
|
9743
|
-
value: fieldToken,
|
|
9744
|
-
what: `<input type="${type}">`
|
|
9745
|
-
};
|
|
9746
|
-
this.report({
|
|
9747
|
-
node,
|
|
9748
|
-
message: getTerminalMessage(context),
|
|
9749
|
-
location: tokens.location(fieldIndex),
|
|
9750
|
-
context
|
|
9751
|
-
});
|
|
9752
|
-
}
|
|
9663
|
+
});
|
|
9753
9664
|
}
|
|
9754
9665
|
}
|
|
9755
9666
|
|
|
9756
|
-
const defaults$
|
|
9757
|
-
|
|
9667
|
+
const defaults$1 = {
|
|
9668
|
+
allowEmpty: true,
|
|
9669
|
+
alias: []
|
|
9758
9670
|
};
|
|
9759
|
-
class
|
|
9671
|
+
class H37 extends Rule {
|
|
9760
9672
|
constructor(options) {
|
|
9761
|
-
super({ ...defaults$
|
|
9673
|
+
super({ ...defaults$1, ...options });
|
|
9674
|
+
if (!Array.isArray(this.options.alias)) {
|
|
9675
|
+
this.options.alias = [this.options.alias];
|
|
9676
|
+
}
|
|
9762
9677
|
}
|
|
9763
9678
|
static schema() {
|
|
9764
9679
|
return {
|
|
9765
|
-
|
|
9680
|
+
alias: {
|
|
9681
|
+
anyOf: [
|
|
9682
|
+
{
|
|
9683
|
+
items: {
|
|
9684
|
+
type: "string"
|
|
9685
|
+
},
|
|
9686
|
+
type: "array"
|
|
9687
|
+
},
|
|
9688
|
+
{
|
|
9689
|
+
type: "string"
|
|
9690
|
+
}
|
|
9691
|
+
]
|
|
9692
|
+
},
|
|
9693
|
+
allowEmpty: {
|
|
9766
9694
|
type: "boolean"
|
|
9767
9695
|
}
|
|
9768
9696
|
};
|
|
9769
9697
|
}
|
|
9770
|
-
documentation(
|
|
9771
|
-
const { relaxed } = this.options;
|
|
9772
|
-
const message = this.messages[context].replace("id", "ID").replace(/^(.)/, (m) => m.toUpperCase());
|
|
9773
|
-
const relaxedDescription = relaxed ? [] : [
|
|
9774
|
-
" - ID must begin with a letter",
|
|
9775
|
-
" - ID must only contain letters, digits, `-` and `_`"
|
|
9776
|
-
];
|
|
9698
|
+
documentation() {
|
|
9777
9699
|
return {
|
|
9778
|
-
description:
|
|
9779
|
-
|
|
9780
|
-
"",
|
|
9781
|
-
"Under the current configuration the following rules are applied:",
|
|
9782
|
-
"",
|
|
9783
|
-
" - ID must not be empty",
|
|
9784
|
-
" - ID must not contain any whitespace characters",
|
|
9785
|
-
...relaxedDescription
|
|
9786
|
-
].join("\n"),
|
|
9787
|
-
url: "https://html-validate.org/rules/valid-id.html"
|
|
9700
|
+
description: "Both HTML5 and WCAG 2.0 requires images to have a alternative text for each image.",
|
|
9701
|
+
url: "https://html-validate.org/rules/wcag/h37.html"
|
|
9788
9702
|
};
|
|
9789
9703
|
}
|
|
9790
9704
|
setup() {
|
|
9791
|
-
this.on("
|
|
9792
|
-
const {
|
|
9793
|
-
|
|
9794
|
-
|
|
9705
|
+
this.on("dom:ready", (event) => {
|
|
9706
|
+
const { document } = event;
|
|
9707
|
+
const nodes = document.querySelectorAll("img");
|
|
9708
|
+
for (const node of nodes) {
|
|
9709
|
+
this.validateNode(node);
|
|
9795
9710
|
}
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9711
|
+
});
|
|
9712
|
+
}
|
|
9713
|
+
validateNode(node) {
|
|
9714
|
+
if (!inAccessibilityTree(node)) {
|
|
9715
|
+
return;
|
|
9716
|
+
}
|
|
9717
|
+
if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
|
|
9718
|
+
return;
|
|
9719
|
+
}
|
|
9720
|
+
for (const attr of this.options.alias) {
|
|
9721
|
+
if (node.getAttribute(attr)) {
|
|
9799
9722
|
return;
|
|
9800
9723
|
}
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9724
|
+
}
|
|
9725
|
+
const tag = node.annotatedName;
|
|
9726
|
+
if (node.hasAttribute("alt")) {
|
|
9727
|
+
const attr = node.getAttribute("alt");
|
|
9728
|
+
this.report(node, `${tag} cannot have empty "alt" attribute`, attr.keyLocation);
|
|
9729
|
+
} else {
|
|
9730
|
+
this.report(node, `${tag} is missing required "alt" attribute`, node.location);
|
|
9731
|
+
}
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
|
|
9735
|
+
var _a;
|
|
9736
|
+
const { enum: validScopes } = (_a = html5.th.attributes) == null ? void 0 : _a.scope;
|
|
9737
|
+
const joinedScopes = naturalJoin(validScopes);
|
|
9738
|
+
class H63 extends Rule {
|
|
9739
|
+
documentation() {
|
|
9740
|
+
return {
|
|
9741
|
+
description: "H63: Using the scope attribute to associate header cells and data cells in data tables",
|
|
9742
|
+
url: "https://html-validate.org/rules/wcag/h63.html"
|
|
9743
|
+
};
|
|
9744
|
+
}
|
|
9745
|
+
setup() {
|
|
9746
|
+
this.on("tag:ready", (event) => {
|
|
9747
|
+
const node = event.target;
|
|
9748
|
+
if (node.tagName !== "th") {
|
|
9804
9749
|
return;
|
|
9805
9750
|
}
|
|
9806
|
-
const
|
|
9807
|
-
|
|
9751
|
+
const scope = node.getAttribute("scope");
|
|
9752
|
+
const value = scope == null ? void 0 : scope.value;
|
|
9753
|
+
if (value instanceof DynamicValue) {
|
|
9808
9754
|
return;
|
|
9809
9755
|
}
|
|
9810
|
-
if (value.
|
|
9811
|
-
const context = 3 /* LEADING_CHARACTER */;
|
|
9812
|
-
this.report(event.target, this.messages[context], event.valueLocation, context);
|
|
9756
|
+
if (value && validScopes.includes(value)) {
|
|
9813
9757
|
return;
|
|
9814
9758
|
}
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
}
|
|
9759
|
+
const message = `<th> element must have a valid scope attribute: ${joinedScopes}`;
|
|
9760
|
+
const location = (scope == null ? void 0 : scope.valueLocation) ?? (scope == null ? void 0 : scope.keyLocation) ?? node.location;
|
|
9761
|
+
this.report(node, message, location);
|
|
9819
9762
|
});
|
|
9820
9763
|
}
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
}
|
|
9829
|
-
isRelevant(event) {
|
|
9830
|
-
return event.key === "id";
|
|
9831
|
-
}
|
|
9832
|
-
}
|
|
9833
|
-
|
|
9834
|
-
class VoidContent extends Rule {
|
|
9835
|
-
documentation(tagName) {
|
|
9836
|
-
const doc = {
|
|
9837
|
-
description: "HTML void elements cannot have any content and must not have content or end tag.",
|
|
9838
|
-
url: "https://html-validate.org/rules/void-content.html"
|
|
9764
|
+
}
|
|
9765
|
+
|
|
9766
|
+
class H67 extends Rule {
|
|
9767
|
+
documentation() {
|
|
9768
|
+
return {
|
|
9769
|
+
description: "A decorative image cannot have a title attribute. Either remove `title` or add a descriptive `alt` text.",
|
|
9770
|
+
url: "https://html-validate.org/rules/wcag/h67.html"
|
|
9839
9771
|
};
|
|
9840
|
-
if (tagName) {
|
|
9841
|
-
doc.description = `<${tagName}> is a void element and must not have content or end tag.`;
|
|
9842
|
-
}
|
|
9843
|
-
return doc;
|
|
9844
9772
|
}
|
|
9845
9773
|
setup() {
|
|
9846
9774
|
this.on("tag:end", (event) => {
|
|
9847
9775
|
const node = event.target;
|
|
9848
|
-
if (!node) {
|
|
9776
|
+
if (!node || node.tagName !== "img") {
|
|
9849
9777
|
return;
|
|
9850
9778
|
}
|
|
9851
|
-
|
|
9779
|
+
const title = node.getAttribute("title");
|
|
9780
|
+
if (!title || title.value === "") {
|
|
9852
9781
|
return;
|
|
9853
9782
|
}
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
`End tag for <${node.tagName}> must be omitted`,
|
|
9858
|
-
node.location,
|
|
9859
|
-
node.tagName
|
|
9860
|
-
);
|
|
9783
|
+
const alt = node.getAttributeValue("alt");
|
|
9784
|
+
if (alt && alt !== "") {
|
|
9785
|
+
return;
|
|
9861
9786
|
}
|
|
9787
|
+
this.report(node, "<img> with empty alt text cannot have title attribute", title.keyLocation);
|
|
9862
9788
|
});
|
|
9863
9789
|
}
|
|
9864
9790
|
}
|
|
9865
9791
|
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
};
|
|
9869
|
-
class VoidStyle extends Rule {
|
|
9870
|
-
constructor(options) {
|
|
9871
|
-
super({ ...defaults$2, ...options });
|
|
9872
|
-
this.style = parseStyle(this.options.style);
|
|
9873
|
-
}
|
|
9874
|
-
static schema() {
|
|
9875
|
-
return {
|
|
9876
|
-
style: {
|
|
9877
|
-
enum: ["omit", "selfclose", "selfclosing"],
|
|
9878
|
-
type: "string"
|
|
9879
|
-
}
|
|
9880
|
-
};
|
|
9881
|
-
}
|
|
9882
|
-
documentation(context) {
|
|
9883
|
-
const [desc, end] = styleDescription(context.style);
|
|
9792
|
+
class H71 extends Rule {
|
|
9793
|
+
documentation() {
|
|
9884
9794
|
return {
|
|
9885
|
-
description:
|
|
9886
|
-
url: "https://html-validate.org/rules/
|
|
9795
|
+
description: "H71: Providing a description for groups of form controls using fieldset and legend elements",
|
|
9796
|
+
url: "https://html-validate.org/rules/wcag/h71.html"
|
|
9887
9797
|
};
|
|
9888
9798
|
}
|
|
9889
9799
|
setup() {
|
|
9890
|
-
this.on("
|
|
9891
|
-
const
|
|
9892
|
-
|
|
9893
|
-
|
|
9800
|
+
this.on("dom:ready", (event) => {
|
|
9801
|
+
const { document } = event;
|
|
9802
|
+
const fieldsets = document.querySelectorAll(this.selector);
|
|
9803
|
+
for (const fieldset of fieldsets) {
|
|
9804
|
+
this.validate(fieldset);
|
|
9894
9805
|
}
|
|
9895
9806
|
});
|
|
9896
9807
|
}
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
if (this.shouldBeOmitted(node)) {
|
|
9902
|
-
this.reportError(
|
|
9903
|
-
node,
|
|
9904
|
-
`Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`
|
|
9905
|
-
);
|
|
9906
|
-
}
|
|
9907
|
-
if (this.shouldBeSelfClosed(node)) {
|
|
9908
|
-
this.reportError(
|
|
9909
|
-
node,
|
|
9910
|
-
`Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`
|
|
9911
|
-
);
|
|
9808
|
+
validate(fieldset) {
|
|
9809
|
+
const legend = fieldset.querySelectorAll("> legend");
|
|
9810
|
+
if (legend.length === 0) {
|
|
9811
|
+
this.reportNode(fieldset);
|
|
9912
9812
|
}
|
|
9913
9813
|
}
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
style: this.style,
|
|
9917
|
-
tagName: node.tagName
|
|
9918
|
-
};
|
|
9919
|
-
super.report(node, message, null, context);
|
|
9920
|
-
}
|
|
9921
|
-
shouldBeOmitted(node) {
|
|
9922
|
-
return this.style === 1 /* AlwaysOmit */ && node.closed === NodeClosed.VoidSelfClosed;
|
|
9814
|
+
reportNode(node) {
|
|
9815
|
+
super.report(node, `${node.annotatedName} must have a <legend> as the first child`);
|
|
9923
9816
|
}
|
|
9924
|
-
|
|
9925
|
-
return this.
|
|
9817
|
+
get selector() {
|
|
9818
|
+
return this.getTagsDerivedFrom("fieldset").join(",");
|
|
9926
9819
|
}
|
|
9927
9820
|
}
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9821
|
+
|
|
9822
|
+
const bundledRules$1 = {
|
|
9823
|
+
"wcag/h30": H30,
|
|
9824
|
+
"wcag/h32": H32,
|
|
9825
|
+
"wcag/h36": H36,
|
|
9826
|
+
"wcag/h37": H37,
|
|
9827
|
+
"wcag/h63": H63,
|
|
9828
|
+
"wcag/h67": H67,
|
|
9829
|
+
"wcag/h71": H71
|
|
9830
|
+
};
|
|
9831
|
+
|
|
9832
|
+
const bundledRules = {
|
|
9833
|
+
"allowed-links": AllowedLinks,
|
|
9834
|
+
"area-alt": AreaAlt,
|
|
9835
|
+
"aria-hidden-body": AriaHiddenBody,
|
|
9836
|
+
"aria-label-misuse": AriaLabelMisuse,
|
|
9837
|
+
"attr-case": AttrCase,
|
|
9838
|
+
"attr-delimiter": AttrDelimiter,
|
|
9839
|
+
"attr-pattern": AttrPattern,
|
|
9840
|
+
"attr-quotes": AttrQuotes,
|
|
9841
|
+
"attr-spacing": AttrSpacing,
|
|
9842
|
+
"attribute-allowed-values": AttributeAllowedValues,
|
|
9843
|
+
"attribute-boolean-style": AttributeBooleanStyle,
|
|
9844
|
+
"attribute-empty-style": AttributeEmptyStyle,
|
|
9845
|
+
"attribute-misuse": AttributeMisuse,
|
|
9846
|
+
"class-pattern": ClassPattern,
|
|
9847
|
+
"close-attr": CloseAttr,
|
|
9848
|
+
"close-order": CloseOrder,
|
|
9849
|
+
deprecated: Deprecated,
|
|
9850
|
+
"deprecated-rule": DeprecatedRule,
|
|
9851
|
+
"doctype-html": NoStyleTag$1,
|
|
9852
|
+
"doctype-style": DoctypeStyle,
|
|
9853
|
+
"element-case": ElementCase,
|
|
9854
|
+
"element-name": ElementName,
|
|
9855
|
+
"element-permitted-content": ElementPermittedContent,
|
|
9856
|
+
"element-permitted-occurrences": ElementPermittedOccurrences,
|
|
9857
|
+
"element-permitted-order": ElementPermittedOrder,
|
|
9858
|
+
"element-permitted-parent": ElementPermittedParent,
|
|
9859
|
+
"element-required-ancestor": ElementRequiredAncestor,
|
|
9860
|
+
"element-required-attributes": ElementRequiredAttributes,
|
|
9861
|
+
"element-required-content": ElementRequiredContent,
|
|
9862
|
+
"empty-heading": EmptyHeading,
|
|
9863
|
+
"empty-title": EmptyTitle,
|
|
9864
|
+
"form-dup-name": FormDupName,
|
|
9865
|
+
"heading-level": HeadingLevel,
|
|
9866
|
+
"hidden-focusable": HiddenFocusable,
|
|
9867
|
+
"id-pattern": IdPattern,
|
|
9868
|
+
"input-attributes": InputAttributes,
|
|
9869
|
+
"input-missing-label": InputMissingLabel,
|
|
9870
|
+
"long-title": LongTitle,
|
|
9871
|
+
"map-dup-name": MapDupName,
|
|
9872
|
+
"map-id-name": MapIdName,
|
|
9873
|
+
"meta-refresh": MetaRefresh,
|
|
9874
|
+
"missing-doctype": MissingDoctype,
|
|
9875
|
+
"multiple-labeled-controls": MultipleLabeledControls,
|
|
9876
|
+
"name-pattern": NamePattern,
|
|
9877
|
+
"no-abstract-role": NoAbstractRole,
|
|
9878
|
+
"no-autoplay": NoAutoplay,
|
|
9879
|
+
"no-conditional-comment": NoConditionalComment,
|
|
9880
|
+
"no-deprecated-attr": NoDeprecatedAttr,
|
|
9881
|
+
"no-dup-attr": NoDupAttr,
|
|
9882
|
+
"no-dup-class": NoDupClass,
|
|
9883
|
+
"no-dup-id": NoDupID,
|
|
9884
|
+
"no-implicit-button-type": NoImplicitButtonType,
|
|
9885
|
+
"no-implicit-input-type": NoImplicitInputType,
|
|
9886
|
+
"no-implicit-close": NoImplicitClose,
|
|
9887
|
+
"no-inline-style": NoInlineStyle,
|
|
9888
|
+
"no-missing-references": NoMissingReferences,
|
|
9889
|
+
"no-multiple-main": NoMultipleMain,
|
|
9890
|
+
"no-raw-characters": NoRawCharacters,
|
|
9891
|
+
"no-redundant-aria-label": NoRedundantAriaLabel,
|
|
9892
|
+
"no-redundant-for": NoRedundantFor,
|
|
9893
|
+
"no-redundant-role": NoRedundantRole,
|
|
9894
|
+
"no-self-closing": NoSelfClosing,
|
|
9895
|
+
"no-style-tag": NoStyleTag,
|
|
9896
|
+
"no-trailing-whitespace": NoTrailingWhitespace,
|
|
9897
|
+
"no-unknown-elements": NoUnknownElements,
|
|
9898
|
+
"no-unused-disable": NoUnusedDisable,
|
|
9899
|
+
"no-utf8-bom": NoUtf8Bom,
|
|
9900
|
+
"prefer-button": PreferButton,
|
|
9901
|
+
"prefer-native-element": PreferNativeElement,
|
|
9902
|
+
"prefer-tbody": PreferTbody,
|
|
9903
|
+
"require-csp-nonce": RequireCSPNonce,
|
|
9904
|
+
"require-sri": RequireSri,
|
|
9905
|
+
"script-element": ScriptElement,
|
|
9906
|
+
"script-type": ScriptType,
|
|
9907
|
+
"svg-focusable": SvgFocusable,
|
|
9908
|
+
"tel-non-breaking": TelNonBreaking,
|
|
9909
|
+
"text-content": TextContent,
|
|
9910
|
+
"unique-landmark": UniqueLandmark,
|
|
9911
|
+
"unrecognized-char-ref": UnknownCharReference,
|
|
9912
|
+
"valid-autocomplete": ValidAutocomplete,
|
|
9913
|
+
"valid-id": ValidID,
|
|
9914
|
+
"void-content": VoidContent,
|
|
9915
|
+
"void-style": VoidStyle,
|
|
9916
|
+
...bundledRules$1
|
|
9917
|
+
};
|
|
9918
|
+
|
|
9919
|
+
const ruleIds = new Set(Object.keys(bundledRules));
|
|
9920
|
+
function ruleExists(ruleId) {
|
|
9921
|
+
return ruleIds.has(ruleId);
|
|
9938
9922
|
}
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9923
|
+
|
|
9924
|
+
function depthFirst(root, callback) {
|
|
9925
|
+
if (root instanceof DOMTree) {
|
|
9926
|
+
if (root.readyState !== "complete") {
|
|
9927
|
+
throw new Error(`Cannot call walk.depthFirst(..) before document is ready`);
|
|
9928
|
+
}
|
|
9929
|
+
root = root.root;
|
|
9930
|
+
}
|
|
9931
|
+
function visit(node) {
|
|
9932
|
+
node.childElements.forEach(visit);
|
|
9933
|
+
if (!node.isRootElement()) {
|
|
9934
|
+
callback(node);
|
|
9935
|
+
}
|
|
9947
9936
|
}
|
|
9937
|
+
visit(root);
|
|
9948
9938
|
}
|
|
9939
|
+
const walk = {
|
|
9940
|
+
depthFirst
|
|
9941
|
+
};
|
|
9949
9942
|
|
|
9950
|
-
class
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9943
|
+
class DOMTree {
|
|
9944
|
+
/**
|
|
9945
|
+
* @internal
|
|
9946
|
+
*/
|
|
9947
|
+
constructor(location) {
|
|
9948
|
+
this.root = HtmlElement.rootNode(location);
|
|
9949
|
+
this.active = this.root;
|
|
9950
|
+
this.doctype = null;
|
|
9951
|
+
this._readyState = "loading";
|
|
9952
|
+
}
|
|
9953
|
+
/**
|
|
9954
|
+
* @internal
|
|
9955
|
+
*/
|
|
9956
|
+
pushActive(node) {
|
|
9957
|
+
this.active = node;
|
|
9958
|
+
}
|
|
9959
|
+
/**
|
|
9960
|
+
* @internal
|
|
9961
|
+
*/
|
|
9962
|
+
popActive() {
|
|
9963
|
+
if (this.active.isRootElement()) {
|
|
9964
|
+
return;
|
|
9965
|
+
}
|
|
9966
|
+
this.active = this.active.parent ?? this.root;
|
|
9967
|
+
}
|
|
9968
|
+
/**
|
|
9969
|
+
* @internal
|
|
9970
|
+
*/
|
|
9971
|
+
getActive() {
|
|
9972
|
+
return this.active;
|
|
9973
|
+
}
|
|
9974
|
+
/**
|
|
9975
|
+
* Describes the loading state of the document.
|
|
9976
|
+
*
|
|
9977
|
+
* When `"loading"` it is still not safe to use functions such as
|
|
9978
|
+
* `querySelector` or presence of attributes, child nodes, etc.
|
|
9979
|
+
*/
|
|
9980
|
+
get readyState() {
|
|
9981
|
+
return this._readyState;
|
|
9956
9982
|
}
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
}
|
|
9967
|
-
const textClassification = classifyNodeText(link, { ignoreHiddenRoot: true });
|
|
9968
|
-
if (textClassification !== TextClassification.EMPTY_TEXT) {
|
|
9969
|
-
continue;
|
|
9970
|
-
}
|
|
9971
|
-
const images = link.querySelectorAll("img");
|
|
9972
|
-
if (images.some((image) => hasAltText(image))) {
|
|
9973
|
-
continue;
|
|
9974
|
-
}
|
|
9975
|
-
const labels = link.querySelectorAll("[aria-label]");
|
|
9976
|
-
if (hasAriaLabel(link) || labels.some((cur) => hasAriaLabel(cur))) {
|
|
9977
|
-
continue;
|
|
9978
|
-
}
|
|
9979
|
-
this.report(link, "Anchor link must have a text describing its purpose");
|
|
9980
|
-
}
|
|
9983
|
+
/**
|
|
9984
|
+
* Resolve dynamic meta expressions.
|
|
9985
|
+
*
|
|
9986
|
+
* @internal
|
|
9987
|
+
*/
|
|
9988
|
+
resolveMeta(table) {
|
|
9989
|
+
this._readyState = "complete";
|
|
9990
|
+
walk.depthFirst(this, (node) => {
|
|
9991
|
+
table.resolve(node);
|
|
9981
9992
|
});
|
|
9982
9993
|
}
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
class H32 extends Rule {
|
|
9986
|
-
documentation() {
|
|
9987
|
-
return {
|
|
9988
|
-
description: "WCAG 2.1 requires each `<form>` element to have at least one submit button.",
|
|
9989
|
-
url: "https://html-validate.org/rules/wcag/h32.html"
|
|
9990
|
-
};
|
|
9994
|
+
getElementsByTagName(tagName) {
|
|
9995
|
+
return this.root.getElementsByTagName(tagName);
|
|
9991
9996
|
}
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
const forms = document.querySelectorAll(formSelector);
|
|
9998
|
-
for (const form of forms) {
|
|
9999
|
-
if (hasNestedSubmit(form)) {
|
|
10000
|
-
continue;
|
|
10001
|
-
}
|
|
10002
|
-
if (hasAssociatedSubmit(document, form)) {
|
|
10003
|
-
continue;
|
|
10004
|
-
}
|
|
10005
|
-
this.report(form, `<${form.tagName}> element must have a submit button`);
|
|
10006
|
-
}
|
|
10007
|
-
});
|
|
9997
|
+
/**
|
|
9998
|
+
* @deprecated use utility function `walk.depthFirst(..)` instead (since 8.21.0).
|
|
9999
|
+
*/
|
|
10000
|
+
visitDepthFirst(callback) {
|
|
10001
|
+
walk.depthFirst(this, callback);
|
|
10008
10002
|
}
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
function isAssociated(id, node) {
|
|
10015
|
-
const form = node.getAttribute("form");
|
|
10016
|
-
return Boolean(form == null ? void 0 : form.valueMatches(id, true));
|
|
10017
|
-
}
|
|
10018
|
-
function hasNestedSubmit(form) {
|
|
10019
|
-
const matches = form.querySelectorAll("button,input").filter(isSubmit).filter((node) => !node.hasAttribute("form"));
|
|
10020
|
-
return matches.length > 0;
|
|
10021
|
-
}
|
|
10022
|
-
function hasAssociatedSubmit(document, form) {
|
|
10023
|
-
const { id } = form;
|
|
10024
|
-
if (!id) {
|
|
10025
|
-
return false;
|
|
10003
|
+
/**
|
|
10004
|
+
* @deprecated use `querySelector(..)` instead (since 8.21.0)
|
|
10005
|
+
*/
|
|
10006
|
+
find(callback) {
|
|
10007
|
+
return this.root.find(callback);
|
|
10026
10008
|
}
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
}
|
|
10030
|
-
|
|
10031
|
-
class H36 extends Rule {
|
|
10032
|
-
documentation() {
|
|
10033
|
-
return {
|
|
10034
|
-
description: [
|
|
10035
|
-
"WCAG 2.1 requires all images used as submit buttons to have a non-empty textual description using the `alt` attribute.",
|
|
10036
|
-
'The alt text cannot be empty (`alt=""`).'
|
|
10037
|
-
].join("\n"),
|
|
10038
|
-
url: "https://html-validate.org/rules/wcag/h36.html"
|
|
10039
|
-
};
|
|
10009
|
+
querySelector(selector) {
|
|
10010
|
+
return this.root.querySelector(selector);
|
|
10040
10011
|
}
|
|
10041
|
-
|
|
10042
|
-
this.
|
|
10043
|
-
const node = event.previous;
|
|
10044
|
-
if (node.tagName !== "input")
|
|
10045
|
-
return;
|
|
10046
|
-
if (node.getAttributeValue("type") !== "image") {
|
|
10047
|
-
return;
|
|
10048
|
-
}
|
|
10049
|
-
if (!inAccessibilityTree(node)) {
|
|
10050
|
-
return;
|
|
10051
|
-
}
|
|
10052
|
-
if (!hasAltText(node)) {
|
|
10053
|
-
const message = "image used as submit button must have non-empty alt text";
|
|
10054
|
-
const alt = node.getAttribute("alt");
|
|
10055
|
-
this.report({
|
|
10056
|
-
node,
|
|
10057
|
-
message,
|
|
10058
|
-
location: alt ? alt.keyLocation : node.location
|
|
10059
|
-
});
|
|
10060
|
-
}
|
|
10061
|
-
});
|
|
10012
|
+
querySelectorAll(selector) {
|
|
10013
|
+
return this.root.querySelectorAll(selector);
|
|
10062
10014
|
}
|
|
10063
10015
|
}
|
|
10064
10016
|
|
|
10065
|
-
const
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10017
|
+
const allowedKeys = ["exclude"];
|
|
10018
|
+
class Validator {
|
|
10019
|
+
/**
|
|
10020
|
+
* Test if element is used in a proper context.
|
|
10021
|
+
*
|
|
10022
|
+
* @param node - Element to test.
|
|
10023
|
+
* @param rules - List of rules.
|
|
10024
|
+
* @returns `true` if element passes all tests.
|
|
10025
|
+
*/
|
|
10026
|
+
static validatePermitted(node, rules) {
|
|
10027
|
+
if (!rules) {
|
|
10028
|
+
return true;
|
|
10074
10029
|
}
|
|
10030
|
+
return rules.some((rule) => {
|
|
10031
|
+
return Validator.validatePermittedRule(node, rule);
|
|
10032
|
+
});
|
|
10075
10033
|
}
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10034
|
+
/**
|
|
10035
|
+
* Test if an element is used the correct amount of times.
|
|
10036
|
+
*
|
|
10037
|
+
* For instance, a `<table>` element can only contain a single `<tbody>`
|
|
10038
|
+
* child. If multiple `<tbody>` exists this test will fail both nodes.
|
|
10039
|
+
* Note that this is called on the parent but will fail the children violating
|
|
10040
|
+
* the rule.
|
|
10041
|
+
*
|
|
10042
|
+
* @param children - Array of children to validate.
|
|
10043
|
+
* @param rules - List of rules of the parent element.
|
|
10044
|
+
* @returns `true` if the parent element of the children passes the test.
|
|
10045
|
+
*/
|
|
10046
|
+
static validateOccurrences(children, rules, cb) {
|
|
10047
|
+
if (!rules) {
|
|
10048
|
+
return true;
|
|
10049
|
+
}
|
|
10050
|
+
let valid = true;
|
|
10051
|
+
for (const rule of rules) {
|
|
10052
|
+
if (typeof rule !== "string") {
|
|
10053
|
+
return false;
|
|
10054
|
+
}
|
|
10055
|
+
const [, category, quantifier] = rule.match(/^(@?.*?)([?*]?)$/);
|
|
10056
|
+
const limit = category && quantifier && parseQuantifier(quantifier);
|
|
10057
|
+
if (limit) {
|
|
10058
|
+
const siblings = children.filter(
|
|
10059
|
+
(cur) => Validator.validatePermittedCategory(cur, rule, true)
|
|
10060
|
+
);
|
|
10061
|
+
if (siblings.length > limit) {
|
|
10062
|
+
for (const child of siblings.slice(limit)) {
|
|
10063
|
+
cb(child, category);
|
|
10088
10064
|
}
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
allowEmpty: {
|
|
10092
|
-
type: "boolean"
|
|
10065
|
+
valid = false;
|
|
10066
|
+
}
|
|
10093
10067
|
}
|
|
10094
|
-
}
|
|
10095
|
-
|
|
10096
|
-
documentation() {
|
|
10097
|
-
return {
|
|
10098
|
-
description: "Both HTML5 and WCAG 2.0 requires images to have a alternative text for each image.",
|
|
10099
|
-
url: "https://html-validate.org/rules/wcag/h37.html"
|
|
10100
|
-
};
|
|
10068
|
+
}
|
|
10069
|
+
return valid;
|
|
10101
10070
|
}
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10071
|
+
/**
|
|
10072
|
+
* Validate elements order.
|
|
10073
|
+
*
|
|
10074
|
+
* Given a parent element with children and metadata containing permitted
|
|
10075
|
+
* order it will validate each children and ensure each one exists in the
|
|
10076
|
+
* specified order.
|
|
10077
|
+
*
|
|
10078
|
+
* For instance, for a `<table>` element the `<caption>` element must come
|
|
10079
|
+
* before a `<thead>` which must come before `<tbody>`.
|
|
10080
|
+
*
|
|
10081
|
+
* @param children - Array of children to validate.
|
|
10082
|
+
*/
|
|
10083
|
+
static validateOrder(children, rules, cb) {
|
|
10084
|
+
if (!rules) {
|
|
10085
|
+
return true;
|
|
10086
|
+
}
|
|
10087
|
+
let i = 0;
|
|
10088
|
+
let prev = null;
|
|
10089
|
+
for (const node of children) {
|
|
10090
|
+
const old = i;
|
|
10091
|
+
while (rules[i] && !Validator.validatePermittedCategory(node, rules[i], true)) {
|
|
10092
|
+
i++;
|
|
10093
|
+
}
|
|
10094
|
+
if (i >= rules.length) {
|
|
10095
|
+
const orderSpecified = rules.find(
|
|
10096
|
+
(cur) => Validator.validatePermittedCategory(node, cur, true)
|
|
10097
|
+
);
|
|
10098
|
+
if (orderSpecified) {
|
|
10099
|
+
cb(node, prev);
|
|
10100
|
+
return false;
|
|
10101
|
+
}
|
|
10102
|
+
i = old;
|
|
10108
10103
|
}
|
|
10104
|
+
prev = node;
|
|
10105
|
+
}
|
|
10106
|
+
return true;
|
|
10107
|
+
}
|
|
10108
|
+
/**
|
|
10109
|
+
* Validate element ancestors.
|
|
10110
|
+
*
|
|
10111
|
+
* Check if an element has the required set of elements. At least one of the
|
|
10112
|
+
* selectors must match.
|
|
10113
|
+
*/
|
|
10114
|
+
static validateAncestors(node, rules) {
|
|
10115
|
+
if (!rules || rules.length === 0) {
|
|
10116
|
+
return true;
|
|
10117
|
+
}
|
|
10118
|
+
return rules.some((rule) => node.closest(rule));
|
|
10119
|
+
}
|
|
10120
|
+
/**
|
|
10121
|
+
* Validate element required content.
|
|
10122
|
+
*
|
|
10123
|
+
* Check if an element has the required set of elements. At least one of the
|
|
10124
|
+
* selectors must match.
|
|
10125
|
+
*
|
|
10126
|
+
* Returns `[]` when valid or a list of required but missing tagnames or
|
|
10127
|
+
* categories.
|
|
10128
|
+
*/
|
|
10129
|
+
static validateRequiredContent(node, rules) {
|
|
10130
|
+
if (!rules || rules.length === 0) {
|
|
10131
|
+
return [];
|
|
10132
|
+
}
|
|
10133
|
+
return rules.filter((tagName) => {
|
|
10134
|
+
const haveMatchingChild = node.childElements.some(
|
|
10135
|
+
(child) => Validator.validatePermittedCategory(child, tagName, false)
|
|
10136
|
+
);
|
|
10137
|
+
return !haveMatchingChild;
|
|
10109
10138
|
});
|
|
10110
10139
|
}
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10140
|
+
/**
|
|
10141
|
+
* Test if an attribute has an allowed value and/or format.
|
|
10142
|
+
*
|
|
10143
|
+
* @param attr - Attribute to test.
|
|
10144
|
+
* @param rules - Element attribute metadta.
|
|
10145
|
+
* @returns `true` if attribute passes all tests.
|
|
10146
|
+
*/
|
|
10147
|
+
static validateAttribute(attr, rules) {
|
|
10148
|
+
const rule = rules[attr.key];
|
|
10149
|
+
if (!rule) {
|
|
10150
|
+
return true;
|
|
10114
10151
|
}
|
|
10115
|
-
|
|
10116
|
-
|
|
10152
|
+
const value = attr.value;
|
|
10153
|
+
if (value instanceof DynamicValue) {
|
|
10154
|
+
return true;
|
|
10117
10155
|
}
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
}
|
|
10156
|
+
const empty = value === null || value === "";
|
|
10157
|
+
if (rule.boolean) {
|
|
10158
|
+
return empty || value === attr.key;
|
|
10122
10159
|
}
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
const attr = node.getAttribute("alt");
|
|
10126
|
-
this.report(node, `${tag} cannot have empty "alt" attribute`, attr.keyLocation);
|
|
10127
|
-
} else {
|
|
10128
|
-
this.report(node, `${tag} is missing required "alt" attribute`, node.location);
|
|
10160
|
+
if (rule.omit && empty) {
|
|
10161
|
+
return true;
|
|
10129
10162
|
}
|
|
10163
|
+
if (rule.list) {
|
|
10164
|
+
const tokens = new DOMTokenList(value, attr.valueLocation);
|
|
10165
|
+
return tokens.every((token) => {
|
|
10166
|
+
return this.validateAttributeValue(token, rule);
|
|
10167
|
+
});
|
|
10168
|
+
}
|
|
10169
|
+
return this.validateAttributeValue(value, rule);
|
|
10130
10170
|
}
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
this.on("tag:ready", (event) => {
|
|
10145
|
-
const node = event.target;
|
|
10146
|
-
if (node.tagName !== "th") {
|
|
10147
|
-
return;
|
|
10148
|
-
}
|
|
10149
|
-
const scope = node.getAttribute("scope");
|
|
10150
|
-
const value = scope == null ? void 0 : scope.value;
|
|
10151
|
-
if (value instanceof DynamicValue) {
|
|
10152
|
-
return;
|
|
10153
|
-
}
|
|
10154
|
-
if (value && validScopes.includes(value)) {
|
|
10155
|
-
return;
|
|
10156
|
-
}
|
|
10157
|
-
const message = `<th> element must have a valid scope attribute: ${joinedScopes}`;
|
|
10158
|
-
const location = (scope == null ? void 0 : scope.valueLocation) ?? (scope == null ? void 0 : scope.keyLocation) ?? node.location;
|
|
10159
|
-
this.report(node, message, location);
|
|
10160
|
-
});
|
|
10161
|
-
}
|
|
10162
|
-
}
|
|
10163
|
-
|
|
10164
|
-
class H67 extends Rule {
|
|
10165
|
-
documentation() {
|
|
10166
|
-
return {
|
|
10167
|
-
description: "A decorative image cannot have a title attribute. Either remove `title` or add a descriptive `alt` text.",
|
|
10168
|
-
url: "https://html-validate.org/rules/wcag/h67.html"
|
|
10169
|
-
};
|
|
10170
|
-
}
|
|
10171
|
-
setup() {
|
|
10172
|
-
this.on("tag:end", (event) => {
|
|
10173
|
-
const node = event.target;
|
|
10174
|
-
if (!node || node.tagName !== "img") {
|
|
10175
|
-
return;
|
|
10176
|
-
}
|
|
10177
|
-
const title = node.getAttribute("title");
|
|
10178
|
-
if (!title || title.value === "") {
|
|
10179
|
-
return;
|
|
10180
|
-
}
|
|
10181
|
-
const alt = node.getAttributeValue("alt");
|
|
10182
|
-
if (alt && alt !== "") {
|
|
10183
|
-
return;
|
|
10171
|
+
static validateAttributeValue(value, rule) {
|
|
10172
|
+
if (!rule.enum) {
|
|
10173
|
+
return true;
|
|
10174
|
+
}
|
|
10175
|
+
if (value === null) {
|
|
10176
|
+
return false;
|
|
10177
|
+
}
|
|
10178
|
+
const caseInsensitiveValue = value.toLowerCase();
|
|
10179
|
+
return rule.enum.some((entry) => {
|
|
10180
|
+
if (entry instanceof RegExp) {
|
|
10181
|
+
return !!value.match(entry);
|
|
10182
|
+
} else {
|
|
10183
|
+
return caseInsensitiveValue === entry;
|
|
10184
10184
|
}
|
|
10185
|
-
this.report(node, "<img> with empty alt text cannot have title attribute", title.keyLocation);
|
|
10186
10185
|
});
|
|
10187
10186
|
}
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
}
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10187
|
+
static validatePermittedRule(node, rule, isExclude = false) {
|
|
10188
|
+
if (typeof rule === "string") {
|
|
10189
|
+
return Validator.validatePermittedCategory(node, rule, !isExclude);
|
|
10190
|
+
} else if (Array.isArray(rule)) {
|
|
10191
|
+
return rule.every((inner) => {
|
|
10192
|
+
return Validator.validatePermittedRule(node, inner, isExclude);
|
|
10193
|
+
});
|
|
10194
|
+
} else {
|
|
10195
|
+
validateKeys(rule);
|
|
10196
|
+
if (rule.exclude) {
|
|
10197
|
+
if (Array.isArray(rule.exclude)) {
|
|
10198
|
+
return !rule.exclude.some((inner) => {
|
|
10199
|
+
return Validator.validatePermittedRule(node, inner, true);
|
|
10200
|
+
});
|
|
10201
|
+
} else {
|
|
10202
|
+
return !Validator.validatePermittedRule(node, rule.exclude, true);
|
|
10203
|
+
}
|
|
10204
|
+
} else {
|
|
10205
|
+
return true;
|
|
10203
10206
|
}
|
|
10204
|
-
}
|
|
10207
|
+
}
|
|
10205
10208
|
}
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10209
|
+
/**
|
|
10210
|
+
* Validate node against a content category.
|
|
10211
|
+
*
|
|
10212
|
+
* When matching parent nodes against permitted parents use the superset
|
|
10213
|
+
* parameter to also match for `@flow`. E.g. if a node expects a `@phrasing`
|
|
10214
|
+
* parent it should also allow `@flow` parent since `@phrasing` is a subset of
|
|
10215
|
+
* `@flow`.
|
|
10216
|
+
*
|
|
10217
|
+
* @param node - The node to test against
|
|
10218
|
+
* @param category - Name of category with `@` prefix or tag name.
|
|
10219
|
+
* @param defaultMatch - The default return value when node categories is not known.
|
|
10220
|
+
*/
|
|
10221
|
+
/* eslint-disable-next-line complexity -- rule does not like switch */
|
|
10222
|
+
static validatePermittedCategory(node, category, defaultMatch) {
|
|
10223
|
+
const [, rawCategory] = category.match(/^(@?.*?)([?*]?)$/);
|
|
10224
|
+
if (!rawCategory.startsWith("@")) {
|
|
10225
|
+
return node.tagName === rawCategory;
|
|
10226
|
+
}
|
|
10227
|
+
if (!node.meta) {
|
|
10228
|
+
return defaultMatch;
|
|
10229
|
+
}
|
|
10230
|
+
switch (rawCategory) {
|
|
10231
|
+
case "@meta":
|
|
10232
|
+
return node.meta.metadata;
|
|
10233
|
+
case "@flow":
|
|
10234
|
+
return node.meta.flow;
|
|
10235
|
+
case "@sectioning":
|
|
10236
|
+
return node.meta.sectioning;
|
|
10237
|
+
case "@heading":
|
|
10238
|
+
return node.meta.heading;
|
|
10239
|
+
case "@phrasing":
|
|
10240
|
+
return node.meta.phrasing;
|
|
10241
|
+
case "@embedded":
|
|
10242
|
+
return node.meta.embedded;
|
|
10243
|
+
case "@interactive":
|
|
10244
|
+
return node.meta.interactive;
|
|
10245
|
+
case "@script":
|
|
10246
|
+
return Boolean(node.meta.scriptSupporting);
|
|
10247
|
+
case "@form":
|
|
10248
|
+
return Boolean(node.meta.form);
|
|
10249
|
+
default:
|
|
10250
|
+
throw new Error(`Invalid content category "${category}"`);
|
|
10210
10251
|
}
|
|
10211
10252
|
}
|
|
10212
|
-
|
|
10213
|
-
|
|
10253
|
+
}
|
|
10254
|
+
function validateKeys(rule) {
|
|
10255
|
+
for (const key of Object.keys(rule)) {
|
|
10256
|
+
if (!allowedKeys.includes(key)) {
|
|
10257
|
+
const str = JSON.stringify(rule);
|
|
10258
|
+
throw new Error(`Permitted rule "${str}" contains unknown property "${key}"`);
|
|
10259
|
+
}
|
|
10214
10260
|
}
|
|
10215
|
-
|
|
10216
|
-
|
|
10261
|
+
}
|
|
10262
|
+
function parseQuantifier(quantifier) {
|
|
10263
|
+
switch (quantifier) {
|
|
10264
|
+
case "?":
|
|
10265
|
+
return 1;
|
|
10266
|
+
case "*":
|
|
10267
|
+
return null;
|
|
10268
|
+
default:
|
|
10269
|
+
throw new Error(`Invalid quantifier "${quantifier}" used`);
|
|
10217
10270
|
}
|
|
10218
10271
|
}
|
|
10219
10272
|
|
|
10220
|
-
const
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10273
|
+
const $schema = "http://json-schema.org/draft-06/schema#";
|
|
10274
|
+
const $id = "https://html-validate.org/schemas/config.json";
|
|
10275
|
+
const type = "object";
|
|
10276
|
+
const additionalProperties = false;
|
|
10277
|
+
const properties = {
|
|
10278
|
+
$schema: {
|
|
10279
|
+
type: "string"
|
|
10280
|
+
},
|
|
10281
|
+
root: {
|
|
10282
|
+
type: "boolean",
|
|
10283
|
+
title: "Mark as root configuration",
|
|
10284
|
+
description: "If this is set to true no further configurations will be searched.",
|
|
10285
|
+
"default": false
|
|
10286
|
+
},
|
|
10287
|
+
"extends": {
|
|
10288
|
+
type: "array",
|
|
10289
|
+
items: {
|
|
10290
|
+
type: "string"
|
|
10291
|
+
},
|
|
10292
|
+
title: "Configurations to extend",
|
|
10293
|
+
description: "Array of shareable or builtin configurations to extend."
|
|
10294
|
+
},
|
|
10295
|
+
elements: {
|
|
10296
|
+
type: "array",
|
|
10297
|
+
items: {
|
|
10298
|
+
anyOf: [
|
|
10299
|
+
{
|
|
10300
|
+
type: "string"
|
|
10301
|
+
},
|
|
10302
|
+
{
|
|
10303
|
+
type: "object"
|
|
10304
|
+
}
|
|
10305
|
+
]
|
|
10306
|
+
},
|
|
10307
|
+
title: "Element metadata to load",
|
|
10308
|
+
description: "Array of modules, plugins or files to load element metadata from. Use <rootDir> to refer to the folder with the package.json file.",
|
|
10309
|
+
examples: [
|
|
10310
|
+
[
|
|
10311
|
+
"html-validate:recommended",
|
|
10312
|
+
"plugin:recommended",
|
|
10313
|
+
"module",
|
|
10314
|
+
"./local-file.json"
|
|
10315
|
+
]
|
|
10316
|
+
]
|
|
10317
|
+
},
|
|
10318
|
+
plugins: {
|
|
10319
|
+
type: "array",
|
|
10320
|
+
items: {
|
|
10321
|
+
anyOf: [
|
|
10322
|
+
{
|
|
10323
|
+
type: "string"
|
|
10324
|
+
},
|
|
10325
|
+
{
|
|
10326
|
+
type: "object"
|
|
10327
|
+
}
|
|
10328
|
+
]
|
|
10329
|
+
},
|
|
10330
|
+
title: "Plugins to load",
|
|
10331
|
+
description: "Array of plugins load. Use <rootDir> to refer to the folder with the package.json file.",
|
|
10332
|
+
examples: [
|
|
10333
|
+
[
|
|
10334
|
+
"my-plugin",
|
|
10335
|
+
"./local-plugin"
|
|
10336
|
+
]
|
|
10337
|
+
]
|
|
10338
|
+
},
|
|
10339
|
+
transform: {
|
|
10340
|
+
type: "object",
|
|
10341
|
+
additionalProperties: {
|
|
10342
|
+
type: "string"
|
|
10343
|
+
},
|
|
10344
|
+
title: "File transformations to use.",
|
|
10345
|
+
description: "Object where key is regular expression to match filename and value is name of transformer.",
|
|
10346
|
+
examples: [
|
|
10347
|
+
{
|
|
10348
|
+
"^.*\\.foo$": "my-transformer",
|
|
10349
|
+
"^.*\\.bar$": "my-plugin",
|
|
10350
|
+
"^.*\\.baz$": "my-plugin:named"
|
|
10351
|
+
}
|
|
10352
|
+
]
|
|
10353
|
+
},
|
|
10354
|
+
rules: {
|
|
10355
|
+
type: "object",
|
|
10356
|
+
patternProperties: {
|
|
10357
|
+
".*": {
|
|
10358
|
+
anyOf: [
|
|
10359
|
+
{
|
|
10360
|
+
"enum": [
|
|
10361
|
+
0,
|
|
10362
|
+
1,
|
|
10363
|
+
2,
|
|
10364
|
+
"off",
|
|
10365
|
+
"warn",
|
|
10366
|
+
"error"
|
|
10367
|
+
]
|
|
10368
|
+
},
|
|
10369
|
+
{
|
|
10370
|
+
type: "array",
|
|
10371
|
+
minItems: 1,
|
|
10372
|
+
maxItems: 1,
|
|
10373
|
+
items: [
|
|
10374
|
+
{
|
|
10375
|
+
"enum": [
|
|
10376
|
+
0,
|
|
10377
|
+
1,
|
|
10378
|
+
2,
|
|
10379
|
+
"off",
|
|
10380
|
+
"warn",
|
|
10381
|
+
"error"
|
|
10382
|
+
]
|
|
10383
|
+
}
|
|
10384
|
+
]
|
|
10385
|
+
},
|
|
10386
|
+
{
|
|
10387
|
+
type: "array",
|
|
10388
|
+
minItems: 2,
|
|
10389
|
+
maxItems: 2,
|
|
10390
|
+
items: [
|
|
10391
|
+
{
|
|
10392
|
+
"enum": [
|
|
10393
|
+
0,
|
|
10394
|
+
1,
|
|
10395
|
+
2,
|
|
10396
|
+
"off",
|
|
10397
|
+
"warn",
|
|
10398
|
+
"error"
|
|
10399
|
+
]
|
|
10400
|
+
},
|
|
10401
|
+
{
|
|
10402
|
+
}
|
|
10403
|
+
]
|
|
10404
|
+
}
|
|
10405
|
+
]
|
|
10406
|
+
}
|
|
10407
|
+
},
|
|
10408
|
+
title: "Rule configuration.",
|
|
10409
|
+
description: "Enable/disable rules, set severity. Some rules have additional configuration like style or patterns to use.",
|
|
10410
|
+
examples: [
|
|
10411
|
+
{
|
|
10412
|
+
foo: "error",
|
|
10413
|
+
bar: "off",
|
|
10414
|
+
baz: [
|
|
10415
|
+
"error",
|
|
10416
|
+
{
|
|
10417
|
+
style: "camelcase"
|
|
10418
|
+
}
|
|
10419
|
+
]
|
|
10420
|
+
}
|
|
10421
|
+
]
|
|
10422
|
+
}
|
|
10423
|
+
};
|
|
10424
|
+
var configurationSchema = {
|
|
10425
|
+
$schema: $schema,
|
|
10426
|
+
$id: $id,
|
|
10427
|
+
type: type,
|
|
10428
|
+
additionalProperties: additionalProperties,
|
|
10429
|
+
properties: properties
|
|
10228
10430
|
};
|
|
10229
10431
|
|
|
10230
|
-
const
|
|
10231
|
-
|
|
10232
|
-
"area-alt": AreaAlt,
|
|
10233
|
-
"aria-hidden-body": AriaHiddenBody,
|
|
10234
|
-
"aria-label-misuse": AriaLabelMisuse,
|
|
10235
|
-
"attr-case": AttrCase,
|
|
10236
|
-
"attr-delimiter": AttrDelimiter,
|
|
10237
|
-
"attr-pattern": AttrPattern,
|
|
10238
|
-
"attr-quotes": AttrQuotes,
|
|
10239
|
-
"attr-spacing": AttrSpacing,
|
|
10240
|
-
"attribute-allowed-values": AttributeAllowedValues,
|
|
10241
|
-
"attribute-boolean-style": AttributeBooleanStyle,
|
|
10242
|
-
"attribute-empty-style": AttributeEmptyStyle,
|
|
10243
|
-
"attribute-misuse": AttributeMisuse,
|
|
10244
|
-
"class-pattern": ClassPattern,
|
|
10245
|
-
"close-attr": CloseAttr,
|
|
10246
|
-
"close-order": CloseOrder,
|
|
10247
|
-
deprecated: Deprecated,
|
|
10248
|
-
"deprecated-rule": DeprecatedRule,
|
|
10249
|
-
"doctype-html": NoStyleTag$1,
|
|
10250
|
-
"doctype-style": DoctypeStyle,
|
|
10251
|
-
"element-case": ElementCase,
|
|
10252
|
-
"element-name": ElementName,
|
|
10253
|
-
"element-permitted-content": ElementPermittedContent,
|
|
10254
|
-
"element-permitted-occurrences": ElementPermittedOccurrences,
|
|
10255
|
-
"element-permitted-order": ElementPermittedOrder,
|
|
10256
|
-
"element-permitted-parent": ElementPermittedParent,
|
|
10257
|
-
"element-required-ancestor": ElementRequiredAncestor,
|
|
10258
|
-
"element-required-attributes": ElementRequiredAttributes,
|
|
10259
|
-
"element-required-content": ElementRequiredContent,
|
|
10260
|
-
"empty-heading": EmptyHeading,
|
|
10261
|
-
"empty-title": EmptyTitle,
|
|
10262
|
-
"form-dup-name": FormDupName,
|
|
10263
|
-
"heading-level": HeadingLevel,
|
|
10264
|
-
"hidden-focusable": HiddenFocusable,
|
|
10265
|
-
"id-pattern": IdPattern,
|
|
10266
|
-
"input-attributes": InputAttributes,
|
|
10267
|
-
"input-missing-label": InputMissingLabel,
|
|
10268
|
-
"long-title": LongTitle,
|
|
10269
|
-
"map-dup-name": MapDupName,
|
|
10270
|
-
"map-id-name": MapIdName,
|
|
10271
|
-
"meta-refresh": MetaRefresh,
|
|
10272
|
-
"missing-doctype": MissingDoctype,
|
|
10273
|
-
"multiple-labeled-controls": MultipleLabeledControls,
|
|
10274
|
-
"name-pattern": NamePattern,
|
|
10275
|
-
"no-abstract-role": NoAbstractRole,
|
|
10276
|
-
"no-autoplay": NoAutoplay,
|
|
10277
|
-
"no-conditional-comment": NoConditionalComment,
|
|
10278
|
-
"no-deprecated-attr": NoDeprecatedAttr,
|
|
10279
|
-
"no-dup-attr": NoDupAttr,
|
|
10280
|
-
"no-dup-class": NoDupClass,
|
|
10281
|
-
"no-dup-id": NoDupID,
|
|
10282
|
-
"no-implicit-button-type": NoImplicitButtonType,
|
|
10283
|
-
"no-implicit-input-type": NoImplicitInputType,
|
|
10284
|
-
"no-implicit-close": NoImplicitClose,
|
|
10285
|
-
"no-inline-style": NoInlineStyle,
|
|
10286
|
-
"no-missing-references": NoMissingReferences,
|
|
10287
|
-
"no-multiple-main": NoMultipleMain,
|
|
10288
|
-
"no-raw-characters": NoRawCharacters,
|
|
10289
|
-
"no-redundant-aria-label": NoRedundantAriaLabel,
|
|
10290
|
-
"no-redundant-for": NoRedundantFor,
|
|
10291
|
-
"no-redundant-role": NoRedundantRole,
|
|
10292
|
-
"no-self-closing": NoSelfClosing,
|
|
10293
|
-
"no-style-tag": NoStyleTag,
|
|
10294
|
-
"no-trailing-whitespace": NoTrailingWhitespace,
|
|
10295
|
-
"no-unknown-elements": NoUnknownElements,
|
|
10296
|
-
"no-unused-disable": NoUnusedDisable,
|
|
10297
|
-
"no-utf8-bom": NoUtf8Bom,
|
|
10298
|
-
"prefer-button": PreferButton,
|
|
10299
|
-
"prefer-native-element": PreferNativeElement,
|
|
10300
|
-
"prefer-tbody": PreferTbody,
|
|
10301
|
-
"require-csp-nonce": RequireCSPNonce,
|
|
10302
|
-
"require-sri": RequireSri,
|
|
10303
|
-
"script-element": ScriptElement,
|
|
10304
|
-
"script-type": ScriptType,
|
|
10305
|
-
"svg-focusable": SvgFocusable,
|
|
10306
|
-
"tel-non-breaking": TelNonBreaking,
|
|
10307
|
-
"text-content": TextContent,
|
|
10308
|
-
"unique-landmark": UniqueLandmark,
|
|
10309
|
-
"unrecognized-char-ref": UnknownCharReference,
|
|
10310
|
-
"valid-autocomplete": ValidAutocomplete,
|
|
10311
|
-
"valid-id": ValidID,
|
|
10312
|
-
"void-content": VoidContent,
|
|
10313
|
-
"void-style": VoidStyle,
|
|
10314
|
-
...bundledRules$1
|
|
10432
|
+
const TRANSFORMER_API = {
|
|
10433
|
+
VERSION: 1
|
|
10315
10434
|
};
|
|
10316
10435
|
|
|
10317
10436
|
var defaultConfig = {};
|
|
@@ -11439,7 +11558,7 @@ class Parser {
|
|
|
11439
11558
|
location
|
|
11440
11559
|
};
|
|
11441
11560
|
this.trigger("tag:end", event);
|
|
11442
|
-
if (
|
|
11561
|
+
if (!active.isRootElement()) {
|
|
11443
11562
|
this.trigger("element:ready", {
|
|
11444
11563
|
target: active,
|
|
11445
11564
|
location: active.location
|
|
@@ -11791,15 +11910,6 @@ class Parser {
|
|
|
11791
11910
|
}
|
|
11792
11911
|
}
|
|
11793
11912
|
|
|
11794
|
-
function isThenable(value) {
|
|
11795
|
-
return value && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
11796
|
-
}
|
|
11797
|
-
|
|
11798
|
-
const ruleIds = new Set(Object.keys(bundledRules));
|
|
11799
|
-
function ruleExists(ruleId) {
|
|
11800
|
-
return ruleIds.has(ruleId);
|
|
11801
|
-
}
|
|
11802
|
-
|
|
11803
11913
|
function freeze(src) {
|
|
11804
11914
|
return {
|
|
11805
11915
|
...src,
|
|
@@ -11970,7 +12080,7 @@ class Engine {
|
|
|
11970
12080
|
const directiveContext = {
|
|
11971
12081
|
rules,
|
|
11972
12082
|
reportUnused(rules2, unused, options, location2) {
|
|
11973
|
-
if (
|
|
12083
|
+
if (!rules2.has(noUnusedDisable.name)) {
|
|
11974
12084
|
noUnusedDisable.reportUnused(unused, options, location2);
|
|
11975
12085
|
}
|
|
11976
12086
|
}
|
|
@@ -12047,33 +12157,8 @@ class Engine {
|
|
|
12047
12157
|
}
|
|
12048
12158
|
dumpTree(source) {
|
|
12049
12159
|
const parser = this.instantiateParser();
|
|
12050
|
-
const
|
|
12051
|
-
|
|
12052
|
-
function decoration(node) {
|
|
12053
|
-
let output = "";
|
|
12054
|
-
if (node.id) {
|
|
12055
|
-
output += `#${node.id}`;
|
|
12056
|
-
}
|
|
12057
|
-
if (node.hasAttribute("class")) {
|
|
12058
|
-
output += `.${node.classList.join(".")}`;
|
|
12059
|
-
}
|
|
12060
|
-
return output;
|
|
12061
|
-
}
|
|
12062
|
-
function writeNode(node, level, sibling) {
|
|
12063
|
-
if (node.parent) {
|
|
12064
|
-
const indent = " ".repeat(level - 1);
|
|
12065
|
-
const l = node.childElements.length > 0 ? "\u252C" : "\u2500";
|
|
12066
|
-
const b = sibling < node.parent.childElements.length - 1 ? "\u251C" : "\u2514";
|
|
12067
|
-
lines.push(`${indent}${b}\u2500${l} ${node.tagName}${decoration(node)}`);
|
|
12068
|
-
} else {
|
|
12069
|
-
lines.push("(root)");
|
|
12070
|
-
}
|
|
12071
|
-
node.childElements.forEach((child, index) => {
|
|
12072
|
-
writeNode(child, level + 1, index);
|
|
12073
|
-
});
|
|
12074
|
-
}
|
|
12075
|
-
writeNode(document, 0, 0);
|
|
12076
|
-
return lines;
|
|
12160
|
+
const root = parser.parseHtml(source[0]);
|
|
12161
|
+
return dumpTree(root);
|
|
12077
12162
|
}
|
|
12078
12163
|
/**
|
|
12079
12164
|
* Get rule documentation.
|
|
@@ -12101,7 +12186,9 @@ class Engine {
|
|
|
12101
12186
|
return new this.ParserClass(this.config);
|
|
12102
12187
|
}
|
|
12103
12188
|
processDirective(event, parser, context) {
|
|
12104
|
-
const rules = event.data.split(",").map((name) => name.trim()).map((name) => context.rules[name]).filter((rule) =>
|
|
12189
|
+
const rules = event.data.split(",").map((name) => name.trim()).map((name) => context.rules[name]).filter((rule) => {
|
|
12190
|
+
return Boolean(rule);
|
|
12191
|
+
});
|
|
12105
12192
|
const location = event.optionsLocation ?? event.location;
|
|
12106
12193
|
switch (event.action) {
|
|
12107
12194
|
case "enable":
|
|
@@ -12125,7 +12212,7 @@ class Engine {
|
|
|
12125
12212
|
rule.setServerity(Severity.ERROR);
|
|
12126
12213
|
}
|
|
12127
12214
|
}
|
|
12128
|
-
parser.on("tag:start", (
|
|
12215
|
+
parser.on("tag:start", (_event, data) => {
|
|
12129
12216
|
data.target.enableRules(rules.map((rule) => rule.name));
|
|
12130
12217
|
});
|
|
12131
12218
|
}
|
|
@@ -12133,7 +12220,7 @@ class Engine {
|
|
|
12133
12220
|
for (const rule of rules) {
|
|
12134
12221
|
rule.setEnabled(false);
|
|
12135
12222
|
}
|
|
12136
|
-
parser.on("tag:start", (
|
|
12223
|
+
parser.on("tag:start", (_event, data) => {
|
|
12137
12224
|
data.target.disableRules(rules.map((rule) => rule.name));
|
|
12138
12225
|
});
|
|
12139
12226
|
}
|
|
@@ -12145,14 +12232,14 @@ class Engine {
|
|
|
12145
12232
|
for (const rule of rules) {
|
|
12146
12233
|
rule.block(blocker);
|
|
12147
12234
|
}
|
|
12148
|
-
const unregisterOpen = parser.on("tag:start", (
|
|
12235
|
+
const unregisterOpen = parser.on("tag:start", (_event, data) => {
|
|
12149
12236
|
var _a;
|
|
12150
12237
|
if (directiveBlock === null) {
|
|
12151
12238
|
directiveBlock = ((_a = data.target.parent) == null ? void 0 : _a.unique) ?? null;
|
|
12152
12239
|
}
|
|
12153
12240
|
data.target.blockRules(ruleIds, blocker);
|
|
12154
12241
|
});
|
|
12155
|
-
const unregisterClose = parser.on("tag:end", (
|
|
12242
|
+
const unregisterClose = parser.on("tag:end", (_event, data) => {
|
|
12156
12243
|
const lastNode = directiveBlock === null;
|
|
12157
12244
|
const parentClosed = directiveBlock === data.previous.unique;
|
|
12158
12245
|
if (lastNode || parentClosed) {
|
|
@@ -12163,7 +12250,7 @@ class Engine {
|
|
|
12163
12250
|
}
|
|
12164
12251
|
}
|
|
12165
12252
|
});
|
|
12166
|
-
parser.on("rule:error", (
|
|
12253
|
+
parser.on("rule:error", (_event, data) => {
|
|
12167
12254
|
if (data.blockers.includes(blocker)) {
|
|
12168
12255
|
unused.delete(data.ruleId);
|
|
12169
12256
|
}
|
|
@@ -12179,10 +12266,10 @@ class Engine {
|
|
|
12179
12266
|
for (const rule of rules) {
|
|
12180
12267
|
rule.block(blocker);
|
|
12181
12268
|
}
|
|
12182
|
-
const unregister = parser.on("tag:start", (
|
|
12269
|
+
const unregister = parser.on("tag:start", (_event, data) => {
|
|
12183
12270
|
data.target.blockRules(ruleIds, blocker);
|
|
12184
12271
|
});
|
|
12185
|
-
parser.on("rule:error", (
|
|
12272
|
+
parser.on("rule:error", (_event, data) => {
|
|
12186
12273
|
if (data.blockers.includes(blocker)) {
|
|
12187
12274
|
unused.delete(data.ruleId);
|
|
12188
12275
|
}
|
|
@@ -12746,7 +12833,7 @@ class HtmlValidate {
|
|
|
12746
12833
|
}
|
|
12747
12834
|
|
|
12748
12835
|
const name = "html-validate";
|
|
12749
|
-
const version = "8.
|
|
12836
|
+
const version = "8.22.0";
|
|
12750
12837
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
12751
12838
|
|
|
12752
12839
|
function definePlugin(plugin) {
|
|
@@ -12834,17 +12921,26 @@ const REPLACERS = [
|
|
|
12834
12921
|
[
|
|
12835
12922
|
// (a\ ) -> (a )
|
|
12836
12923
|
// (a ) -> (a)
|
|
12924
|
+
// (a ) -> (a)
|
|
12837
12925
|
// (a \ ) -> (a )
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12926
|
+
/((?:\\\\)*?)(\\?\s+)$/,
|
|
12927
|
+
(_, m1, m2) => m1 + (
|
|
12928
|
+
m2.indexOf('\\') === 0
|
|
12929
|
+
? SPACE
|
|
12930
|
+
: EMPTY
|
|
12931
|
+
)
|
|
12842
12932
|
],
|
|
12843
12933
|
|
|
12844
12934
|
// replace (\ ) with ' '
|
|
12935
|
+
// (\ ) -> ' '
|
|
12936
|
+
// (\\ ) -> '\\ '
|
|
12937
|
+
// (\\\ ) -> '\\ '
|
|
12845
12938
|
[
|
|
12846
|
-
|
|
12847
|
-
() =>
|
|
12939
|
+
/(\\+?)\s/g,
|
|
12940
|
+
(_, m1) => {
|
|
12941
|
+
const {length} = m1;
|
|
12942
|
+
return m1.slice(0, length - length % 2) + SPACE
|
|
12943
|
+
}
|
|
12848
12944
|
],
|
|
12849
12945
|
|
|
12850
12946
|
// Escape metacharacters
|
|
@@ -13072,7 +13168,8 @@ const makeRegex = (pattern, ignoreCase) => {
|
|
|
13072
13168
|
|
|
13073
13169
|
if (!source) {
|
|
13074
13170
|
source = REPLACERS.reduce(
|
|
13075
|
-
(prev,
|
|
13171
|
+
(prev, [matcher, replacer]) =>
|
|
13172
|
+
prev.replace(matcher, replacer.bind(pattern)),
|
|
13076
13173
|
pattern
|
|
13077
13174
|
);
|
|
13078
13175
|
regexCache[pattern] = source;
|
|
@@ -13438,6 +13535,81 @@ const defaults = {
|
|
|
13438
13535
|
showSummary: true,
|
|
13439
13536
|
showSelector: false
|
|
13440
13537
|
};
|
|
13538
|
+
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
|
|
13539
|
+
function getMarkerLines(loc, source) {
|
|
13540
|
+
const startLoc = {
|
|
13541
|
+
...loc.start
|
|
13542
|
+
};
|
|
13543
|
+
const endLoc = {
|
|
13544
|
+
...startLoc,
|
|
13545
|
+
...loc.end
|
|
13546
|
+
};
|
|
13547
|
+
const linesAbove = 2;
|
|
13548
|
+
const linesBelow = 3;
|
|
13549
|
+
const startLine = startLoc.line;
|
|
13550
|
+
const startColumn = startLoc.column;
|
|
13551
|
+
const endLine = endLoc.line;
|
|
13552
|
+
const endColumn = endLoc.column;
|
|
13553
|
+
const start = Math.max(startLine - (linesAbove + 1), 0);
|
|
13554
|
+
const end = Math.min(source.length, endLine + linesBelow);
|
|
13555
|
+
const lineDiff = endLine - startLine;
|
|
13556
|
+
const markerLines = {};
|
|
13557
|
+
if (lineDiff) {
|
|
13558
|
+
for (let i = 0; i <= lineDiff; i++) {
|
|
13559
|
+
const lineNumber = i + startLine;
|
|
13560
|
+
if (!startColumn) {
|
|
13561
|
+
markerLines[lineNumber] = true;
|
|
13562
|
+
} else if (i === 0) {
|
|
13563
|
+
const sourceLength = source[lineNumber - 1].length;
|
|
13564
|
+
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
|
|
13565
|
+
} else if (i === lineDiff) {
|
|
13566
|
+
markerLines[lineNumber] = [0, endColumn];
|
|
13567
|
+
} else {
|
|
13568
|
+
const sourceLength = source[lineNumber - i].length;
|
|
13569
|
+
markerLines[lineNumber] = [0, sourceLength];
|
|
13570
|
+
}
|
|
13571
|
+
}
|
|
13572
|
+
} else {
|
|
13573
|
+
if (startColumn === endColumn) {
|
|
13574
|
+
if (startColumn) {
|
|
13575
|
+
markerLines[startLine] = [startColumn, 0];
|
|
13576
|
+
} else {
|
|
13577
|
+
markerLines[startLine] = true;
|
|
13578
|
+
}
|
|
13579
|
+
} else {
|
|
13580
|
+
markerLines[startLine] = [startColumn, endColumn - startColumn];
|
|
13581
|
+
}
|
|
13582
|
+
}
|
|
13583
|
+
return { start, end, markerLines };
|
|
13584
|
+
}
|
|
13585
|
+
function codeFrameColumns(rawLines, loc) {
|
|
13586
|
+
const lines = rawLines.split(NEWLINE);
|
|
13587
|
+
const { start, end, markerLines } = getMarkerLines(loc, lines);
|
|
13588
|
+
const numberMaxWidth = String(end).length;
|
|
13589
|
+
return rawLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
|
|
13590
|
+
const number = start + 1 + index;
|
|
13591
|
+
const paddedNumber = ` ${String(number)}`.slice(-numberMaxWidth);
|
|
13592
|
+
const gutter = ` ${paddedNumber} |`;
|
|
13593
|
+
const hasMarker = markerLines[number];
|
|
13594
|
+
if (hasMarker) {
|
|
13595
|
+
let markerLine = "";
|
|
13596
|
+
if (Array.isArray(hasMarker)) {
|
|
13597
|
+
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
|
|
13598
|
+
const numberOfMarkers = hasMarker[1] || 1;
|
|
13599
|
+
markerLine = [
|
|
13600
|
+
"\n ",
|
|
13601
|
+
gutter.replace(/\d/g, " "),
|
|
13602
|
+
" ",
|
|
13603
|
+
markerSpacing,
|
|
13604
|
+
"^".repeat(numberOfMarkers)
|
|
13605
|
+
].join("");
|
|
13606
|
+
}
|
|
13607
|
+
return [">", gutter, line.length > 0 ? ` ${line}` : "", markerLine].join("");
|
|
13608
|
+
} else {
|
|
13609
|
+
return [" ", gutter, line.length > 0 ? ` ${line}` : ""].join("");
|
|
13610
|
+
}
|
|
13611
|
+
}).join("\n");
|
|
13612
|
+
}
|
|
13441
13613
|
function pluralize(word, count) {
|
|
13442
13614
|
return count === 1 ? word : `${word}s`;
|
|
13443
13615
|
}
|
|
@@ -13480,16 +13652,11 @@ function formatMessage(message, parentResult, options) {
|
|
|
13480
13652
|
].filter(String).join(" ");
|
|
13481
13653
|
const result = [firstLine];
|
|
13482
13654
|
if (sourceCode) {
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
end: getEndLocation(message, sourceCode)
|
|
13489
|
-
},
|
|
13490
|
-
{ highlightCode: false }
|
|
13491
|
-
)
|
|
13492
|
-
);
|
|
13655
|
+
const output = codeFrameColumns(sourceCode, {
|
|
13656
|
+
start: getStartLocation(message),
|
|
13657
|
+
end: getEndLocation(message, sourceCode)
|
|
13658
|
+
});
|
|
13659
|
+
result.push(output);
|
|
13493
13660
|
}
|
|
13494
13661
|
if (options.showSelector) {
|
|
13495
13662
|
result.push(`${kleur.bold("Selector:")} ${message.selector ?? "-"}`);
|
|
@@ -13621,5 +13788,5 @@ function compatibilityCheckImpl(name, declared, options) {
|
|
|
13621
13788
|
return false;
|
|
13622
13789
|
}
|
|
13623
13790
|
|
|
13624
|
-
export { Attribute as A, definePlugin as B, Config as C, DOMNode as D, Parser as E, ruleExists as F,
|
|
13791
|
+
export { Attribute as A, definePlugin as B, Config as C, DOMNode as D, Parser as E, ruleExists as F, walk as G, HtmlValidate as H, EventHandler as I, compatibilityCheckImpl as J, codeframe as K, name as L, MetaCopyableProperty as M, NodeClosed as N, bugs as O, 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 };
|
|
13625
13792
|
//# sourceMappingURL=core.js.map
|