html-validate 10.14.0 → 10.15.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 +306 -176
- package/dist/cjs/core.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 +306 -177
- package/dist/esm/core.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
|
*
|
|
@@ -12471,8 +12502,10 @@ class StaticConfigLoader extends ConfigLoader {
|
|
|
12471
12502
|
|
|
12472
12503
|
class EventHandler {
|
|
12473
12504
|
listeners;
|
|
12505
|
+
tracker;
|
|
12474
12506
|
constructor() {
|
|
12475
12507
|
this.listeners = {};
|
|
12508
|
+
this.tracker = null;
|
|
12476
12509
|
}
|
|
12477
12510
|
/**
|
|
12478
12511
|
* Add an event listener.
|
|
@@ -12511,6 +12544,14 @@ class EventHandler {
|
|
|
12511
12544
|
});
|
|
12512
12545
|
return deregister;
|
|
12513
12546
|
}
|
|
12547
|
+
/**
|
|
12548
|
+
* Set (or clear) the performance tracker.
|
|
12549
|
+
*
|
|
12550
|
+
* @internal
|
|
12551
|
+
*/
|
|
12552
|
+
setTracker(tracker) {
|
|
12553
|
+
this.tracker = tracker;
|
|
12554
|
+
}
|
|
12514
12555
|
/**
|
|
12515
12556
|
* Trigger event causing all listeners to be called.
|
|
12516
12557
|
*
|
|
@@ -12519,8 +12560,18 @@ class EventHandler {
|
|
|
12519
12560
|
*/
|
|
12520
12561
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- technical debt, should be made typesafe */
|
|
12521
12562
|
trigger(event, data) {
|
|
12522
|
-
|
|
12523
|
-
|
|
12563
|
+
const { tracker } = this;
|
|
12564
|
+
if (tracker) {
|
|
12565
|
+
const start = performance.now();
|
|
12566
|
+
for (const listener of this.getCallbacks(event)) {
|
|
12567
|
+
listener.call(null, event, data);
|
|
12568
|
+
}
|
|
12569
|
+
const end = performance.now();
|
|
12570
|
+
tracker.trackEvent(event, end - start);
|
|
12571
|
+
} else {
|
|
12572
|
+
for (const listener of this.getCallbacks(event)) {
|
|
12573
|
+
listener.call(null, event, data);
|
|
12574
|
+
}
|
|
12524
12575
|
}
|
|
12525
12576
|
}
|
|
12526
12577
|
getCallbacks(event) {
|
|
@@ -12532,7 +12583,7 @@ class EventHandler {
|
|
|
12532
12583
|
}
|
|
12533
12584
|
|
|
12534
12585
|
const name = "html-validate";
|
|
12535
|
-
const version = "10.
|
|
12586
|
+
const version = "10.15.0";
|
|
12536
12587
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
12537
12588
|
|
|
12538
12589
|
function freeze(src) {
|
|
@@ -13466,6 +13517,78 @@ class Parser {
|
|
|
13466
13517
|
}
|
|
13467
13518
|
}
|
|
13468
13519
|
|
|
13520
|
+
class PerformanceTracker {
|
|
13521
|
+
eventData;
|
|
13522
|
+
ruleData;
|
|
13523
|
+
startTime;
|
|
13524
|
+
accConfigTime;
|
|
13525
|
+
accTransformTime;
|
|
13526
|
+
constructor() {
|
|
13527
|
+
this.eventData = /* @__PURE__ */ new Map();
|
|
13528
|
+
this.ruleData = /* @__PURE__ */ new Map();
|
|
13529
|
+
this.startTime = performance.now();
|
|
13530
|
+
this.accConfigTime = 0;
|
|
13531
|
+
this.accTransformTime = 0;
|
|
13532
|
+
}
|
|
13533
|
+
/**
|
|
13534
|
+
* Record a single event trigger with the time it took to run all listeners.
|
|
13535
|
+
*/
|
|
13536
|
+
trackEvent(name, time) {
|
|
13537
|
+
const existing = this.eventData.get(name);
|
|
13538
|
+
if (existing) {
|
|
13539
|
+
existing.count += 1;
|
|
13540
|
+
existing.time += time;
|
|
13541
|
+
} else {
|
|
13542
|
+
this.eventData.set(name, { count: 1, time });
|
|
13543
|
+
}
|
|
13544
|
+
}
|
|
13545
|
+
/**
|
|
13546
|
+
* Record time spent loading configuration.
|
|
13547
|
+
*/
|
|
13548
|
+
trackConfig(time) {
|
|
13549
|
+
this.accConfigTime += time;
|
|
13550
|
+
}
|
|
13551
|
+
/**
|
|
13552
|
+
* Record time spent in transformers.
|
|
13553
|
+
*/
|
|
13554
|
+
trackTransform(time) {
|
|
13555
|
+
this.accTransformTime += time;
|
|
13556
|
+
}
|
|
13557
|
+
/**
|
|
13558
|
+
* Record a single rule callback invocation with its execution time.
|
|
13559
|
+
*/
|
|
13560
|
+
trackRule(ruleName, time) {
|
|
13561
|
+
const existing = this.ruleData.get(ruleName);
|
|
13562
|
+
if (existing) {
|
|
13563
|
+
existing.count += 1;
|
|
13564
|
+
existing.time += time;
|
|
13565
|
+
} else {
|
|
13566
|
+
this.ruleData.set(ruleName, { count: 1, time });
|
|
13567
|
+
}
|
|
13568
|
+
}
|
|
13569
|
+
/**
|
|
13570
|
+
* Returns a snapshot of the recorded performance data, with both arrays
|
|
13571
|
+
* sorted by time (descending).
|
|
13572
|
+
*/
|
|
13573
|
+
getResult() {
|
|
13574
|
+
const events = Array.from(
|
|
13575
|
+
this.eventData.entries(),
|
|
13576
|
+
([event, { count, time }]) => ({ event, count, time })
|
|
13577
|
+
).toSorted((a, b) => b.time - a.time);
|
|
13578
|
+
const rules = Array.from(
|
|
13579
|
+
this.ruleData.entries(),
|
|
13580
|
+
([rule, { count, time }]) => ({ rule, count, time })
|
|
13581
|
+
).toSorted((a, b) => b.time - a.time);
|
|
13582
|
+
return {
|
|
13583
|
+
events,
|
|
13584
|
+
rules,
|
|
13585
|
+
configTime: this.accConfigTime,
|
|
13586
|
+
transformTime: this.accTransformTime,
|
|
13587
|
+
totalTime: performance.now() - this.startTime
|
|
13588
|
+
};
|
|
13589
|
+
}
|
|
13590
|
+
}
|
|
13591
|
+
|
|
13469
13592
|
const ruleIds = new Set(Object.keys(bundledRules));
|
|
13470
13593
|
function ruleExists(ruleId) {
|
|
13471
13594
|
return ruleIds.has(ruleId);
|
|
@@ -13513,10 +13636,12 @@ class Engine {
|
|
|
13513
13636
|
config;
|
|
13514
13637
|
ParserClass;
|
|
13515
13638
|
availableRules;
|
|
13516
|
-
|
|
13639
|
+
tracker;
|
|
13640
|
+
constructor(config, ParserClass, options) {
|
|
13517
13641
|
this.report = new Reporter();
|
|
13518
13642
|
this.config = config;
|
|
13519
13643
|
this.ParserClass = ParserClass;
|
|
13644
|
+
this.tracker = options.tracker;
|
|
13520
13645
|
const result = this.initPlugins(this.config);
|
|
13521
13646
|
this.availableRules = {
|
|
13522
13647
|
...bundledRules,
|
|
@@ -13532,6 +13657,9 @@ class Engine {
|
|
|
13532
13657
|
lint(sources) {
|
|
13533
13658
|
for (const source of sources) {
|
|
13534
13659
|
const parser = this.instantiateParser();
|
|
13660
|
+
if (this.tracker) {
|
|
13661
|
+
parser.getEventHandler().setTracker(this.tracker);
|
|
13662
|
+
}
|
|
13535
13663
|
const { rules } = this.setupPlugins(source, this.config, parser);
|
|
13536
13664
|
const noUnusedDisable = rules["no-unused-disable"];
|
|
13537
13665
|
const directiveContext = {
|
|
@@ -13808,6 +13936,7 @@ class Engine {
|
|
|
13808
13936
|
const rule = this.instantiateRule(ruleId, options);
|
|
13809
13937
|
rule.name = ruleId;
|
|
13810
13938
|
rule.init(parser, report, severity, meta);
|
|
13939
|
+
rule.setTracker(this.tracker);
|
|
13811
13940
|
if (rule.setup) {
|
|
13812
13941
|
rule.setup();
|
|
13813
13942
|
}
|
|
@@ -15183,6 +15312,7 @@ exports.NestedError = NestedError;
|
|
|
15183
15312
|
exports.NodeClosed = NodeClosed;
|
|
15184
15313
|
exports.NodeType = NodeType;
|
|
15185
15314
|
exports.Parser = Parser;
|
|
15315
|
+
exports.PerformanceTracker = PerformanceTracker;
|
|
15186
15316
|
exports.Reporter = Reporter;
|
|
15187
15317
|
exports.ResolvedConfig = ResolvedConfig;
|
|
15188
15318
|
exports.Rule = Rule;
|