html-validate 10.14.0 → 10.16.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 -0
- package/dist/cjs/browser.js.map +1 -1
- package/dist/cjs/cli.js +50 -4
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core-browser.js +60 -11
- package/dist/cjs/core-browser.js.map +1 -1
- package/dist/cjs/core-nodejs.js +60 -11
- package/dist/cjs/core-nodejs.js.map +1 -1
- package/dist/cjs/core.js +353 -204
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/elements.js +9 -0
- package/dist/cjs/elements.js.map +1 -1
- package/dist/cjs/html-validate.js +4 -1
- package/dist/cjs/html-validate.js.map +1 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/browser.js +1 -1
- package/dist/esm/cli.js +51 -5
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/core-browser.js +61 -12
- package/dist/esm/core-browser.js.map +1 -1
- package/dist/esm/core-nodejs.js +61 -12
- package/dist/esm/core-nodejs.js.map +1 -1
- package/dist/esm/core.js +353 -205
- package/dist/esm/core.js.map +1 -1
- package/dist/esm/elements.js +9 -0
- package/dist/esm/elements.js.map +1 -1
- package/dist/esm/html-validate.js +5 -2
- package/dist/esm/html-validate.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/matcher-utils.js +1 -1
- package/dist/esm/matchers.js +1 -1
- package/dist/types/browser.d.ts +15 -0
- package/dist/types/index.d.ts +15 -0
- package/package.json +1 -1
package/dist/cjs/core.js
CHANGED
|
@@ -1204,9 +1204,6 @@ function clone(value) {
|
|
|
1204
1204
|
return JSON.parse(JSON.stringify(value));
|
|
1205
1205
|
}
|
|
1206
1206
|
}
|
|
1207
|
-
function overwriteMerge$1(_a, b) {
|
|
1208
|
-
return b;
|
|
1209
|
-
}
|
|
1210
1207
|
class MetaTable {
|
|
1211
1208
|
elements;
|
|
1212
1209
|
schema;
|
|
@@ -1379,15 +1376,22 @@ class MetaTable {
|
|
|
1379
1376
|
}
|
|
1380
1377
|
}
|
|
1381
1378
|
mergeElement(a, b) {
|
|
1382
|
-
const merged =
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1379
|
+
const merged = { ...a, ...b };
|
|
1380
|
+
const mergedAttrs = {
|
|
1381
|
+
...a.attributes,
|
|
1382
|
+
...b.attributes
|
|
1383
|
+
};
|
|
1384
|
+
for (const [name, attr] of Object.entries(mergedAttrs)) {
|
|
1385
|
+
if (attr.delete) {
|
|
1386
|
+
delete mergedAttrs[name];
|
|
1387
|
+
} else {
|
|
1388
|
+
delete attr.delete;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
merged.attributes = mergedAttrs;
|
|
1392
|
+
if (a.aria) {
|
|
1393
|
+
merged.aria = { ...a.aria, ...b.aria };
|
|
1394
|
+
}
|
|
1391
1395
|
return merged;
|
|
1392
1396
|
}
|
|
1393
1397
|
/**
|
|
@@ -1968,71 +1972,68 @@ function factory(name, context) {
|
|
|
1968
1972
|
function stripslashes(value) {
|
|
1969
1973
|
return value.replaceAll(/\\(.)/g, "$1");
|
|
1970
1974
|
}
|
|
1971
|
-
|
|
1975
|
+
function createClassCondition(classname) {
|
|
1976
|
+
return {
|
|
1977
|
+
kind: "class",
|
|
1978
|
+
classname,
|
|
1979
|
+
match(node) {
|
|
1980
|
+
return node.classList.contains(classname);
|
|
1981
|
+
}
|
|
1982
|
+
};
|
|
1972
1983
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
}
|
|
1984
|
+
function createIdCondition(raw) {
|
|
1985
|
+
const id = stripslashes(raw);
|
|
1986
|
+
return {
|
|
1987
|
+
kind: "id",
|
|
1988
|
+
id,
|
|
1989
|
+
match(node) {
|
|
1990
|
+
return node.id === id;
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1982
1993
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1994
|
+
function createAttributeCondition(attr) {
|
|
1995
|
+
const match = /^(.+?)(?:([$*^|~]?=)"([^"]+?)")?$/.exec(attr);
|
|
1996
|
+
const key = match[1];
|
|
1997
|
+
const op = match[2];
|
|
1998
|
+
const rawValue = match[3];
|
|
1999
|
+
const value = typeof rawValue === "string" ? stripslashes(rawValue) : rawValue;
|
|
2000
|
+
return {
|
|
2001
|
+
kind: "attribute",
|
|
2002
|
+
key,
|
|
2003
|
+
op,
|
|
2004
|
+
value,
|
|
2005
|
+
match(node) {
|
|
2006
|
+
const attrs = node.getAttribute(key, true);
|
|
2007
|
+
return attrs.some((cur) => {
|
|
2008
|
+
switch (op) {
|
|
2009
|
+
case void 0:
|
|
2010
|
+
return true;
|
|
2011
|
+
/* attribute exists */
|
|
2012
|
+
case "=":
|
|
2013
|
+
return cur.value === value;
|
|
2014
|
+
default:
|
|
2015
|
+
throw new Error(`Attribute selector operator ${op} is not implemented yet`);
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
1992
2020
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
constructor(attr) {
|
|
1998
|
-
super();
|
|
1999
|
-
const [, key, op, value] = /^(.+?)(?:([$*^|~]?=)"([^"]+?)")?$/.exec(attr);
|
|
2000
|
-
this.key = key;
|
|
2001
|
-
this.op = op;
|
|
2002
|
-
this.value = typeof value === "string" ? stripslashes(value) : value;
|
|
2003
|
-
}
|
|
2004
|
-
match(node) {
|
|
2005
|
-
const attr = node.getAttribute(this.key, true);
|
|
2006
|
-
return attr.some((cur) => {
|
|
2007
|
-
switch (this.op) {
|
|
2008
|
-
case void 0:
|
|
2009
|
-
return true;
|
|
2010
|
-
/* attribute exists */
|
|
2011
|
-
case "=":
|
|
2012
|
-
return cur.value === this.value;
|
|
2013
|
-
default:
|
|
2014
|
-
throw new Error(`Attribute selector operator ${this.op} is not implemented yet`);
|
|
2015
|
-
}
|
|
2016
|
-
});
|
|
2021
|
+
function createPseudoClassCondition(pseudoclass, context) {
|
|
2022
|
+
const match = /^([^(]+)(?:\((.*)\))?$/.exec(pseudoclass);
|
|
2023
|
+
if (!match) {
|
|
2024
|
+
throw new Error(`Missing pseudo-class after colon in selector pattern "${context}"`);
|
|
2017
2025
|
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2026
|
+
const name = match[1];
|
|
2027
|
+
const args = match[2];
|
|
2028
|
+
return {
|
|
2029
|
+
kind: "pseudo",
|
|
2030
|
+
name,
|
|
2031
|
+
args,
|
|
2032
|
+
match(node, selectorContext) {
|
|
2033
|
+
const fn = factory(name, selectorContext);
|
|
2034
|
+
return fn(node, args);
|
|
2027
2035
|
}
|
|
2028
|
-
|
|
2029
|
-
this.name = name;
|
|
2030
|
-
this.args = args;
|
|
2031
|
-
}
|
|
2032
|
-
match(node, context) {
|
|
2033
|
-
const fn = factory(this.name, context);
|
|
2034
|
-
return fn(node, this.args);
|
|
2035
|
-
}
|
|
2036
|
+
};
|
|
2036
2037
|
}
|
|
2037
2038
|
|
|
2038
2039
|
function isDelimiter(ch) {
|
|
@@ -2107,37 +2108,84 @@ class Compound {
|
|
|
2107
2108
|
createCondition(pattern) {
|
|
2108
2109
|
switch (pattern[0]) {
|
|
2109
2110
|
case ".":
|
|
2110
|
-
return
|
|
2111
|
+
return createClassCondition(pattern.slice(1));
|
|
2111
2112
|
case "#":
|
|
2112
|
-
return
|
|
2113
|
+
return createIdCondition(pattern.slice(1));
|
|
2113
2114
|
case "[":
|
|
2114
|
-
return
|
|
2115
|
+
return createAttributeCondition(pattern.slice(1, -1));
|
|
2115
2116
|
case ":":
|
|
2116
|
-
return
|
|
2117
|
+
return createPseudoClassCondition(pattern.slice(1), this.selector);
|
|
2117
2118
|
default:
|
|
2118
2119
|
throw new Error(`Failed to create selector condition for "${pattern}"`);
|
|
2119
2120
|
}
|
|
2120
2121
|
}
|
|
2121
2122
|
}
|
|
2122
2123
|
|
|
2123
|
-
const
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
if (codepoints[ch]) {
|
|
2131
|
-
return codepoints[ch];
|
|
2132
|
-
} else {
|
|
2133
|
-
return `\\${ch}`;
|
|
2124
|
+
const escapedCodepoints = /* @__PURE__ */ new Set(["9", "a", "d"]);
|
|
2125
|
+
function* splitSelectorElements(selector) {
|
|
2126
|
+
let begin = 0;
|
|
2127
|
+
let end = 0;
|
|
2128
|
+
function initialState(ch, p) {
|
|
2129
|
+
if (ch === "\\") {
|
|
2130
|
+
return 1 /* ESCAPED */;
|
|
2134
2131
|
}
|
|
2135
|
-
|
|
2132
|
+
if (ch === " ") {
|
|
2133
|
+
end = p;
|
|
2134
|
+
return 2 /* WHITESPACE */;
|
|
2135
|
+
}
|
|
2136
|
+
return 0 /* INITIAL */;
|
|
2137
|
+
}
|
|
2138
|
+
function escapedState(ch) {
|
|
2139
|
+
if (escapedCodepoints.has(ch)) {
|
|
2140
|
+
return 1 /* ESCAPED */;
|
|
2141
|
+
}
|
|
2142
|
+
return 0 /* INITIAL */;
|
|
2143
|
+
}
|
|
2144
|
+
function* whitespaceState(ch, p) {
|
|
2145
|
+
if (ch === " ") {
|
|
2146
|
+
return 2 /* WHITESPACE */;
|
|
2147
|
+
}
|
|
2148
|
+
yield selector.slice(begin, end);
|
|
2149
|
+
begin = p;
|
|
2150
|
+
end = p;
|
|
2151
|
+
return 0 /* INITIAL */;
|
|
2152
|
+
}
|
|
2153
|
+
let state = 0 /* INITIAL */;
|
|
2154
|
+
for (let p = 0; p < selector.length; p++) {
|
|
2155
|
+
const ch = selector[p];
|
|
2156
|
+
switch (state) {
|
|
2157
|
+
case 0 /* INITIAL */:
|
|
2158
|
+
state = initialState(ch, p);
|
|
2159
|
+
break;
|
|
2160
|
+
case 1 /* ESCAPED */:
|
|
2161
|
+
state = escapedState(ch);
|
|
2162
|
+
break;
|
|
2163
|
+
case 2 /* WHITESPACE */:
|
|
2164
|
+
state = yield* whitespaceState(ch, p);
|
|
2165
|
+
break;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (begin !== selector.length) {
|
|
2169
|
+
yield selector.slice(begin);
|
|
2170
|
+
}
|
|
2136
2171
|
}
|
|
2137
2172
|
|
|
2138
|
-
function
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2173
|
+
function unescapeCodepoint(value) {
|
|
2174
|
+
const replacement = {
|
|
2175
|
+
"\\9 ": " ",
|
|
2176
|
+
"\\a ": "\n",
|
|
2177
|
+
"\\d ": "\r"
|
|
2178
|
+
};
|
|
2179
|
+
return value.replaceAll(
|
|
2180
|
+
/(\\[9ad] )/g,
|
|
2181
|
+
(_, codepoint) => replacement[codepoint]
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
function getCompounds(selector) {
|
|
2185
|
+
selector = selector.replaceAll(/([+>~]) /g, "$1");
|
|
2186
|
+
return Array.from(splitSelectorElements(selector), (element) => {
|
|
2187
|
+
return new Compound(unescapeCodepoint(element));
|
|
2188
|
+
});
|
|
2141
2189
|
}
|
|
2142
2190
|
|
|
2143
2191
|
function* ancestors$1(element) {
|
|
@@ -2202,70 +2250,16 @@ function matchElement(element, compounds, context) {
|
|
|
2202
2250
|
return false;
|
|
2203
2251
|
}
|
|
2204
2252
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
function initialState(ch, p) {
|
|
2210
|
-
if (ch === "\\") {
|
|
2211
|
-
return 1 /* ESCAPED */;
|
|
2212
|
-
}
|
|
2213
|
-
if (ch === " ") {
|
|
2214
|
-
end = p;
|
|
2215
|
-
return 2 /* WHITESPACE */;
|
|
2216
|
-
}
|
|
2217
|
-
return 0 /* INITIAL */;
|
|
2253
|
+
class ComplexSelector {
|
|
2254
|
+
compounds;
|
|
2255
|
+
constructor(compounds) {
|
|
2256
|
+
this.compounds = compounds;
|
|
2218
2257
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
return 1 /* ESCAPED */;
|
|
2222
|
-
}
|
|
2223
|
-
return 0 /* INITIAL */;
|
|
2258
|
+
static fromString(selector) {
|
|
2259
|
+
return new ComplexSelector(getCompounds(selector));
|
|
2224
2260
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
return 2 /* WHITESPACE */;
|
|
2228
|
-
}
|
|
2229
|
-
yield selector.slice(begin, end);
|
|
2230
|
-
begin = p;
|
|
2231
|
-
end = p;
|
|
2232
|
-
return 0 /* INITIAL */;
|
|
2233
|
-
}
|
|
2234
|
-
let state = 0 /* INITIAL */;
|
|
2235
|
-
for (let p = 0; p < selector.length; p++) {
|
|
2236
|
-
const ch = selector[p];
|
|
2237
|
-
switch (state) {
|
|
2238
|
-
case 0 /* INITIAL */:
|
|
2239
|
-
state = initialState(ch, p);
|
|
2240
|
-
break;
|
|
2241
|
-
case 1 /* ESCAPED */:
|
|
2242
|
-
state = escapedState(ch);
|
|
2243
|
-
break;
|
|
2244
|
-
case 2 /* WHITESPACE */:
|
|
2245
|
-
state = yield* whitespaceState(ch, p);
|
|
2246
|
-
break;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
if (begin !== selector.length) {
|
|
2250
|
-
yield selector.slice(begin);
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
function unescapeCodepoint(value) {
|
|
2255
|
-
const replacement = {
|
|
2256
|
-
"\\9 ": " ",
|
|
2257
|
-
"\\a ": "\n",
|
|
2258
|
-
"\\d ": "\r"
|
|
2259
|
-
};
|
|
2260
|
-
return value.replaceAll(
|
|
2261
|
-
/(\\[9ad] )/g,
|
|
2262
|
-
(_, codepoint) => replacement[codepoint]
|
|
2263
|
-
);
|
|
2264
|
-
}
|
|
2265
|
-
class Selector {
|
|
2266
|
-
pattern;
|
|
2267
|
-
constructor(selector) {
|
|
2268
|
-
this.pattern = Selector.parse(selector);
|
|
2261
|
+
static fromCompounds(compounds) {
|
|
2262
|
+
return new ComplexSelector(compounds);
|
|
2269
2263
|
}
|
|
2270
2264
|
/**
|
|
2271
2265
|
* Match this selector against a HtmlElement.
|
|
@@ -2282,15 +2276,15 @@ class Selector {
|
|
|
2282
2276
|
*/
|
|
2283
2277
|
matchElement(element) {
|
|
2284
2278
|
const context = { scope: null };
|
|
2285
|
-
return matchElement(element, this.
|
|
2279
|
+
return matchElement(element, this.compounds, context);
|
|
2286
2280
|
}
|
|
2287
2281
|
*matchInternal(root, level, context) {
|
|
2288
|
-
if (level >= this.
|
|
2282
|
+
if (level >= this.compounds.length) {
|
|
2289
2283
|
yield root;
|
|
2290
2284
|
return;
|
|
2291
2285
|
}
|
|
2292
|
-
const pattern = this.
|
|
2293
|
-
const matches =
|
|
2286
|
+
const pattern = this.compounds[level];
|
|
2287
|
+
const matches = ComplexSelector.findCandidates(root, pattern);
|
|
2294
2288
|
for (const node of matches) {
|
|
2295
2289
|
if (!pattern.match(node, context)) {
|
|
2296
2290
|
continue;
|
|
@@ -2298,12 +2292,6 @@ class Selector {
|
|
|
2298
2292
|
yield* this.matchInternal(node, level + 1, context);
|
|
2299
2293
|
}
|
|
2300
2294
|
}
|
|
2301
|
-
static parse(selector) {
|
|
2302
|
-
selector = selector.replaceAll(/([+>~]) /g, "$1");
|
|
2303
|
-
return Array.from(splitSelectorElements(selector), (element) => {
|
|
2304
|
-
return new Compound(unescapeCodepoint(element));
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
2295
|
static findCandidates(root, pattern) {
|
|
2308
2296
|
switch (pattern.combinator) {
|
|
2309
2297
|
case Combinator.DESCENDANT:
|
|
@@ -2311,9 +2299,9 @@ class Selector {
|
|
|
2311
2299
|
case Combinator.CHILD:
|
|
2312
2300
|
return root.childElements.filter((node) => node.is(pattern.tagName));
|
|
2313
2301
|
case Combinator.ADJACENT_SIBLING:
|
|
2314
|
-
return
|
|
2302
|
+
return ComplexSelector.findAdjacentSibling(root);
|
|
2315
2303
|
case Combinator.GENERAL_SIBLING:
|
|
2316
|
-
return
|
|
2304
|
+
return ComplexSelector.findGeneralSibling(root);
|
|
2317
2305
|
case Combinator.SCOPE:
|
|
2318
2306
|
return [root];
|
|
2319
2307
|
}
|
|
@@ -2325,7 +2313,7 @@ class Selector {
|
|
|
2325
2313
|
adjacent = false;
|
|
2326
2314
|
return true;
|
|
2327
2315
|
}
|
|
2328
|
-
if (cur
|
|
2316
|
+
if (cur.isSameNode(node)) {
|
|
2329
2317
|
adjacent = true;
|
|
2330
2318
|
}
|
|
2331
2319
|
return false;
|
|
@@ -2337,7 +2325,7 @@ class Selector {
|
|
|
2337
2325
|
if (after) {
|
|
2338
2326
|
return true;
|
|
2339
2327
|
}
|
|
2340
|
-
if (cur
|
|
2328
|
+
if (cur.isSameNode(node)) {
|
|
2341
2329
|
after = true;
|
|
2342
2330
|
}
|
|
2343
2331
|
return false;
|
|
@@ -2345,6 +2333,31 @@ class Selector {
|
|
|
2345
2333
|
}
|
|
2346
2334
|
}
|
|
2347
2335
|
|
|
2336
|
+
const codepoints = {
|
|
2337
|
+
" ": "\\9 ",
|
|
2338
|
+
"\n": "\\a ",
|
|
2339
|
+
"\r": "\\d "
|
|
2340
|
+
};
|
|
2341
|
+
function escapeSelectorComponent(text) {
|
|
2342
|
+
return text.toString().replaceAll(/([\t\n\r]|[^\w-])/gi, (_, ch) => {
|
|
2343
|
+
if (codepoints[ch]) {
|
|
2344
|
+
return codepoints[ch];
|
|
2345
|
+
} else {
|
|
2346
|
+
return `\\${ch}`;
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
function generateIdSelector(id) {
|
|
2352
|
+
const escaped = escapeSelectorComponent(id);
|
|
2353
|
+
return /^\d/.test(escaped) ? `[id="${escaped}"]` : `#${escaped}`;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
function parseSelector(selector) {
|
|
2357
|
+
const compounds = getCompounds(selector);
|
|
2358
|
+
return ComplexSelector.fromCompounds(compounds);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2348
2361
|
const TEXT_NODE_NAME = "#text";
|
|
2349
2362
|
function isTextNode(node) {
|
|
2350
2363
|
return node?.nodeType === NodeType.TEXT_NODE;
|
|
@@ -2658,7 +2671,7 @@ class HtmlElement extends DOMNode {
|
|
|
2658
2671
|
*/
|
|
2659
2672
|
matches(selectorList) {
|
|
2660
2673
|
return selectorList.split(",").some((it) => {
|
|
2661
|
-
const selector =
|
|
2674
|
+
const selector = parseSelector(it.trim());
|
|
2662
2675
|
return selector.matchElement(this);
|
|
2663
2676
|
});
|
|
2664
2677
|
}
|
|
@@ -2891,9 +2904,9 @@ class HtmlElement extends DOMNode {
|
|
|
2891
2904
|
if (!selectorList) {
|
|
2892
2905
|
return;
|
|
2893
2906
|
}
|
|
2894
|
-
for (const
|
|
2895
|
-
const
|
|
2896
|
-
yield*
|
|
2907
|
+
for (const selectorString of selectorList.split(/(?<!\\),\s*/)) {
|
|
2908
|
+
const selector = parseSelector(selectorString);
|
|
2909
|
+
yield* selector.match(this);
|
|
2897
2910
|
}
|
|
2898
2911
|
}
|
|
2899
2912
|
/**
|
|
@@ -3746,6 +3759,7 @@ class Rule {
|
|
|
3746
3759
|
severity;
|
|
3747
3760
|
// rule severity
|
|
3748
3761
|
event;
|
|
3762
|
+
tracker;
|
|
3749
3763
|
/**
|
|
3750
3764
|
* Rule name. Defaults to filename without extension but can be overwritten by
|
|
3751
3765
|
* subclasses.
|
|
@@ -3765,6 +3779,7 @@ class Rule {
|
|
|
3765
3779
|
this.blockers = [];
|
|
3766
3780
|
this.severity = Severity.DISABLED;
|
|
3767
3781
|
this.name = "";
|
|
3782
|
+
this.tracker = null;
|
|
3768
3783
|
}
|
|
3769
3784
|
getSeverity() {
|
|
3770
3785
|
return this.severity;
|
|
@@ -3940,7 +3955,15 @@ class Rule {
|
|
|
3940
3955
|
return this.parser.on(event, (_event, data) => {
|
|
3941
3956
|
if (this.isEnabled() && filter(data)) {
|
|
3942
3957
|
this.event = data;
|
|
3943
|
-
|
|
3958
|
+
const { tracker } = this;
|
|
3959
|
+
if (tracker) {
|
|
3960
|
+
const start = performance.now();
|
|
3961
|
+
callback(data);
|
|
3962
|
+
const end = performance.now();
|
|
3963
|
+
tracker.trackRule(this.name, end - start);
|
|
3964
|
+
} else {
|
|
3965
|
+
callback(data);
|
|
3966
|
+
}
|
|
3944
3967
|
}
|
|
3945
3968
|
});
|
|
3946
3969
|
}
|
|
@@ -3957,6 +3980,14 @@ class Rule {
|
|
|
3957
3980
|
this.severity = severity;
|
|
3958
3981
|
this.meta = meta;
|
|
3959
3982
|
}
|
|
3983
|
+
/**
|
|
3984
|
+
* Set (or clear) the performance tracker.
|
|
3985
|
+
*
|
|
3986
|
+
* @internal
|
|
3987
|
+
*/
|
|
3988
|
+
setTracker(tracker) {
|
|
3989
|
+
this.tracker = tracker;
|
|
3990
|
+
}
|
|
3960
3991
|
/**
|
|
3961
3992
|
* Validate rule options against schema. Throws error if object does not validate.
|
|
3962
3993
|
*
|
|
@@ -4339,24 +4370,27 @@ const defaults$A = {
|
|
|
4339
4370
|
}
|
|
4340
4371
|
};
|
|
4341
4372
|
const allowlist = /* @__PURE__ */ new Set([
|
|
4342
|
-
|
|
4343
|
-
"
|
|
4344
|
-
"table",
|
|
4345
|
-
"td",
|
|
4346
|
-
"th",
|
|
4373
|
+
/* landmark elements */
|
|
4374
|
+
"article",
|
|
4347
4375
|
"aside",
|
|
4348
|
-
"header",
|
|
4349
4376
|
"footer",
|
|
4377
|
+
"form",
|
|
4378
|
+
"header",
|
|
4379
|
+
"main",
|
|
4380
|
+
"nav",
|
|
4381
|
+
"search",
|
|
4350
4382
|
"section",
|
|
4351
|
-
|
|
4383
|
+
/* other allowed elements */
|
|
4384
|
+
"area",
|
|
4352
4385
|
"dialog",
|
|
4353
|
-
"
|
|
4386
|
+
"fieldset",
|
|
4387
|
+
"figure",
|
|
4354
4388
|
"iframe",
|
|
4355
4389
|
"img",
|
|
4356
|
-
"area",
|
|
4357
|
-
"fieldset",
|
|
4358
4390
|
"summary",
|
|
4359
|
-
"
|
|
4391
|
+
"table",
|
|
4392
|
+
"td",
|
|
4393
|
+
"th"
|
|
4360
4394
|
]);
|
|
4361
4395
|
function isValidUsage(target, meta) {
|
|
4362
4396
|
const explicit = meta.attributes["aria-label"];
|
|
@@ -7686,7 +7720,7 @@ class InputMissingLabel extends Rule {
|
|
|
7686
7720
|
if (hasAccessibleName(root, elem)) {
|
|
7687
7721
|
return;
|
|
7688
7722
|
}
|
|
7689
|
-
let label
|
|
7723
|
+
let label;
|
|
7690
7724
|
if ((label = findLabelById(root, elem.id)).length > 0) {
|
|
7691
7725
|
this.validateLabel(root, elem, label);
|
|
7692
7726
|
return;
|
|
@@ -8400,7 +8434,8 @@ class NoImplicitInputType extends Rule {
|
|
|
8400
8434
|
const defaults$g = {
|
|
8401
8435
|
include: null,
|
|
8402
8436
|
exclude: null,
|
|
8403
|
-
allowedProperties: ["display"]
|
|
8437
|
+
allowedProperties: ["display"],
|
|
8438
|
+
allowVariables: true
|
|
8404
8439
|
};
|
|
8405
8440
|
class NoInlineStyle extends Rule {
|
|
8406
8441
|
constructor(options) {
|
|
@@ -8439,17 +8474,26 @@ class NoInlineStyle extends Rule {
|
|
|
8439
8474
|
type: "string"
|
|
8440
8475
|
},
|
|
8441
8476
|
type: "array"
|
|
8477
|
+
},
|
|
8478
|
+
allowVariables: {
|
|
8479
|
+
type: "boolean"
|
|
8442
8480
|
}
|
|
8443
8481
|
};
|
|
8444
8482
|
}
|
|
8445
8483
|
documentation() {
|
|
8484
|
+
const { allowVariables, allowedProperties } = this.options;
|
|
8446
8485
|
const text = [
|
|
8447
8486
|
"Inline style is not allowed.\n",
|
|
8448
8487
|
"Inline style is a sign of unstructured CSS. Use class or ID with a separate stylesheet.\n"
|
|
8449
8488
|
];
|
|
8450
|
-
if (
|
|
8489
|
+
if (allowedProperties.length > 0 || allowVariables) {
|
|
8451
8490
|
text.push("Under the current configuration the following CSS properties are allowed:\n");
|
|
8452
|
-
|
|
8491
|
+
}
|
|
8492
|
+
if (allowedProperties.length > 0) {
|
|
8493
|
+
text.push(allowedProperties.map((it) => `- \`${it}\``).join("\n"));
|
|
8494
|
+
}
|
|
8495
|
+
if (allowVariables) {
|
|
8496
|
+
text.push("- CSS variables (custom properties starting with `--`).\n");
|
|
8453
8497
|
}
|
|
8454
8498
|
return {
|
|
8455
8499
|
description: text.join("\n"),
|
|
@@ -8484,13 +8528,16 @@ class NoInlineStyle extends Rule {
|
|
|
8484
8528
|
return true;
|
|
8485
8529
|
}
|
|
8486
8530
|
allPropertiesAllowed(value) {
|
|
8487
|
-
const
|
|
8488
|
-
if (
|
|
8531
|
+
const { allowedProperties, allowVariables } = this.options;
|
|
8532
|
+
if (allowedProperties.length === 0 && !allowVariables) {
|
|
8489
8533
|
return false;
|
|
8490
8534
|
}
|
|
8491
8535
|
const declarations = Object.keys(parseCssDeclaration(value));
|
|
8492
8536
|
return declarations.length > 0 && declarations.every((it) => {
|
|
8493
|
-
|
|
8537
|
+
if (allowVariables && it.startsWith("--")) {
|
|
8538
|
+
return true;
|
|
8539
|
+
}
|
|
8540
|
+
return allowedProperties.includes(it);
|
|
8494
8541
|
});
|
|
8495
8542
|
}
|
|
8496
8543
|
}
|
|
@@ -8593,7 +8640,7 @@ class NoMultipleMain extends Rule {
|
|
|
8593
8640
|
const defaults$f = {
|
|
8594
8641
|
relaxed: false
|
|
8595
8642
|
};
|
|
8596
|
-
const textRegexp = /(
|
|
8643
|
+
const textRegexp = /(<|&(?![\d#A-Za-z]+;))/g;
|
|
8597
8644
|
const unquotedAttrRegexp = /(["'<=>`]|&(?![\d#A-Za-z]+;))/g;
|
|
8598
8645
|
const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
|
|
8599
8646
|
const replacementTable = {
|
|
@@ -8620,7 +8667,7 @@ class NoRawCharacters extends Rule {
|
|
|
8620
8667
|
}
|
|
8621
8668
|
documentation() {
|
|
8622
8669
|
return {
|
|
8623
|
-
description: `Some characters such as
|
|
8670
|
+
description: `Some characters such as \`<\` and \`&\` hold special meaning in HTML and must be escaped using a character reference (HTML entity).`,
|
|
8624
8671
|
url: "https://html-validate.org/rules/no-raw-characters.html"
|
|
8625
8672
|
};
|
|
8626
8673
|
}
|
|
@@ -10035,14 +10082,17 @@ class UnknownCharReference extends Rule {
|
|
|
10035
10082
|
if (found && (terminated || !requireSemicolon)) {
|
|
10036
10083
|
return;
|
|
10037
10084
|
}
|
|
10038
|
-
if (
|
|
10039
|
-
const
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
entity
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10085
|
+
if (!terminated) {
|
|
10086
|
+
const isKnownName = found || this.entities.includes(`${entity};`);
|
|
10087
|
+
if (isKnownName) {
|
|
10088
|
+
const entityLocation2 = getLocation(location, entity, match);
|
|
10089
|
+
const message2 = `Character reference "{{ entity }}" must be terminated by a semicolon`;
|
|
10090
|
+
const context2 = {
|
|
10091
|
+
entity: raw,
|
|
10092
|
+
terminated: false
|
|
10093
|
+
};
|
|
10094
|
+
this.report(node, message2, entityLocation2, context2);
|
|
10095
|
+
}
|
|
10046
10096
|
return;
|
|
10047
10097
|
}
|
|
10048
10098
|
const entityLocation = getLocation(location, entity, match);
|
|
@@ -12471,8 +12521,10 @@ class StaticConfigLoader extends ConfigLoader {
|
|
|
12471
12521
|
|
|
12472
12522
|
class EventHandler {
|
|
12473
12523
|
listeners;
|
|
12524
|
+
tracker;
|
|
12474
12525
|
constructor() {
|
|
12475
12526
|
this.listeners = {};
|
|
12527
|
+
this.tracker = null;
|
|
12476
12528
|
}
|
|
12477
12529
|
/**
|
|
12478
12530
|
* Add an event listener.
|
|
@@ -12511,6 +12563,14 @@ class EventHandler {
|
|
|
12511
12563
|
});
|
|
12512
12564
|
return deregister;
|
|
12513
12565
|
}
|
|
12566
|
+
/**
|
|
12567
|
+
* Set (or clear) the performance tracker.
|
|
12568
|
+
*
|
|
12569
|
+
* @internal
|
|
12570
|
+
*/
|
|
12571
|
+
setTracker(tracker) {
|
|
12572
|
+
this.tracker = tracker;
|
|
12573
|
+
}
|
|
12514
12574
|
/**
|
|
12515
12575
|
* Trigger event causing all listeners to be called.
|
|
12516
12576
|
*
|
|
@@ -12519,8 +12579,18 @@ class EventHandler {
|
|
|
12519
12579
|
*/
|
|
12520
12580
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- technical debt, should be made typesafe */
|
|
12521
12581
|
trigger(event, data) {
|
|
12522
|
-
|
|
12523
|
-
|
|
12582
|
+
const { tracker } = this;
|
|
12583
|
+
if (tracker) {
|
|
12584
|
+
const start = performance.now();
|
|
12585
|
+
for (const listener of this.getCallbacks(event)) {
|
|
12586
|
+
listener.call(null, event, data);
|
|
12587
|
+
}
|
|
12588
|
+
const end = performance.now();
|
|
12589
|
+
tracker.trackEvent(event, end - start);
|
|
12590
|
+
} else {
|
|
12591
|
+
for (const listener of this.getCallbacks(event)) {
|
|
12592
|
+
listener.call(null, event, data);
|
|
12593
|
+
}
|
|
12524
12594
|
}
|
|
12525
12595
|
}
|
|
12526
12596
|
getCallbacks(event) {
|
|
@@ -12532,7 +12602,7 @@ class EventHandler {
|
|
|
12532
12602
|
}
|
|
12533
12603
|
|
|
12534
12604
|
const name = "html-validate";
|
|
12535
|
-
const version = "10.
|
|
12605
|
+
const version = "10.16.0";
|
|
12536
12606
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
12537
12607
|
|
|
12538
12608
|
function freeze(src) {
|
|
@@ -13466,6 +13536,78 @@ class Parser {
|
|
|
13466
13536
|
}
|
|
13467
13537
|
}
|
|
13468
13538
|
|
|
13539
|
+
class PerformanceTracker {
|
|
13540
|
+
eventData;
|
|
13541
|
+
ruleData;
|
|
13542
|
+
startTime;
|
|
13543
|
+
accConfigTime;
|
|
13544
|
+
accTransformTime;
|
|
13545
|
+
constructor() {
|
|
13546
|
+
this.eventData = /* @__PURE__ */ new Map();
|
|
13547
|
+
this.ruleData = /* @__PURE__ */ new Map();
|
|
13548
|
+
this.startTime = performance.now();
|
|
13549
|
+
this.accConfigTime = 0;
|
|
13550
|
+
this.accTransformTime = 0;
|
|
13551
|
+
}
|
|
13552
|
+
/**
|
|
13553
|
+
* Record a single event trigger with the time it took to run all listeners.
|
|
13554
|
+
*/
|
|
13555
|
+
trackEvent(name, time) {
|
|
13556
|
+
const existing = this.eventData.get(name);
|
|
13557
|
+
if (existing) {
|
|
13558
|
+
existing.count += 1;
|
|
13559
|
+
existing.time += time;
|
|
13560
|
+
} else {
|
|
13561
|
+
this.eventData.set(name, { count: 1, time });
|
|
13562
|
+
}
|
|
13563
|
+
}
|
|
13564
|
+
/**
|
|
13565
|
+
* Record time spent loading configuration.
|
|
13566
|
+
*/
|
|
13567
|
+
trackConfig(time) {
|
|
13568
|
+
this.accConfigTime += time;
|
|
13569
|
+
}
|
|
13570
|
+
/**
|
|
13571
|
+
* Record time spent in transformers.
|
|
13572
|
+
*/
|
|
13573
|
+
trackTransform(time) {
|
|
13574
|
+
this.accTransformTime += time;
|
|
13575
|
+
}
|
|
13576
|
+
/**
|
|
13577
|
+
* Record a single rule callback invocation with its execution time.
|
|
13578
|
+
*/
|
|
13579
|
+
trackRule(ruleName, time) {
|
|
13580
|
+
const existing = this.ruleData.get(ruleName);
|
|
13581
|
+
if (existing) {
|
|
13582
|
+
existing.count += 1;
|
|
13583
|
+
existing.time += time;
|
|
13584
|
+
} else {
|
|
13585
|
+
this.ruleData.set(ruleName, { count: 1, time });
|
|
13586
|
+
}
|
|
13587
|
+
}
|
|
13588
|
+
/**
|
|
13589
|
+
* Returns a snapshot of the recorded performance data, with both arrays
|
|
13590
|
+
* sorted by time (descending).
|
|
13591
|
+
*/
|
|
13592
|
+
getResult() {
|
|
13593
|
+
const events = Array.from(
|
|
13594
|
+
this.eventData.entries(),
|
|
13595
|
+
([event, { count, time }]) => ({ event, count, time })
|
|
13596
|
+
).toSorted((a, b) => b.time - a.time);
|
|
13597
|
+
const rules = Array.from(
|
|
13598
|
+
this.ruleData.entries(),
|
|
13599
|
+
([rule, { count, time }]) => ({ rule, count, time })
|
|
13600
|
+
).toSorted((a, b) => b.time - a.time);
|
|
13601
|
+
return {
|
|
13602
|
+
events,
|
|
13603
|
+
rules,
|
|
13604
|
+
configTime: this.accConfigTime,
|
|
13605
|
+
transformTime: this.accTransformTime,
|
|
13606
|
+
totalTime: performance.now() - this.startTime
|
|
13607
|
+
};
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
13610
|
+
|
|
13469
13611
|
const ruleIds = new Set(Object.keys(bundledRules));
|
|
13470
13612
|
function ruleExists(ruleId) {
|
|
13471
13613
|
return ruleIds.has(ruleId);
|
|
@@ -13513,10 +13655,12 @@ class Engine {
|
|
|
13513
13655
|
config;
|
|
13514
13656
|
ParserClass;
|
|
13515
13657
|
availableRules;
|
|
13516
|
-
|
|
13658
|
+
tracker;
|
|
13659
|
+
constructor(config, ParserClass, options) {
|
|
13517
13660
|
this.report = new Reporter();
|
|
13518
13661
|
this.config = config;
|
|
13519
13662
|
this.ParserClass = ParserClass;
|
|
13663
|
+
this.tracker = options.tracker;
|
|
13520
13664
|
const result = this.initPlugins(this.config);
|
|
13521
13665
|
this.availableRules = {
|
|
13522
13666
|
...bundledRules,
|
|
@@ -13532,6 +13676,9 @@ class Engine {
|
|
|
13532
13676
|
lint(sources) {
|
|
13533
13677
|
for (const source of sources) {
|
|
13534
13678
|
const parser = this.instantiateParser();
|
|
13679
|
+
if (this.tracker) {
|
|
13680
|
+
parser.getEventHandler().setTracker(this.tracker);
|
|
13681
|
+
}
|
|
13535
13682
|
const { rules } = this.setupPlugins(source, this.config, parser);
|
|
13536
13683
|
const noUnusedDisable = rules["no-unused-disable"];
|
|
13537
13684
|
const directiveContext = {
|
|
@@ -13808,6 +13955,7 @@ class Engine {
|
|
|
13808
13955
|
const rule = this.instantiateRule(ruleId, options);
|
|
13809
13956
|
rule.name = ruleId;
|
|
13810
13957
|
rule.init(parser, report, severity, meta);
|
|
13958
|
+
rule.setTracker(this.tracker);
|
|
13811
13959
|
if (rule.setup) {
|
|
13812
13960
|
rule.setup();
|
|
13813
13961
|
}
|
|
@@ -15183,6 +15331,7 @@ exports.NestedError = NestedError;
|
|
|
15183
15331
|
exports.NodeClosed = NodeClosed;
|
|
15184
15332
|
exports.NodeType = NodeType;
|
|
15185
15333
|
exports.Parser = Parser;
|
|
15334
|
+
exports.PerformanceTracker = PerformanceTracker;
|
|
15186
15335
|
exports.Reporter = Reporter;
|
|
15187
15336
|
exports.ResolvedConfig = ResolvedConfig;
|
|
15188
15337
|
exports.Rule = Rule;
|