html-validate 10.13.1 → 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 +420 -198
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/elements.js +71 -4
- 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/cjs/tsdoc-metadata.json +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 +420 -199
- package/dist/esm/core.js.map +1 -1
- package/dist/esm/elements.js +71 -4
- 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/tsdoc-metadata.json +1 -1
- package/dist/types/browser.d.ts +42 -0
- package/dist/types/index.d.ts +42 -0
- package/package.json +1 -1
package/dist/esm/core.js
CHANGED
|
@@ -1195,9 +1195,6 @@ function clone(value) {
|
|
|
1195
1195
|
return JSON.parse(JSON.stringify(value));
|
|
1196
1196
|
}
|
|
1197
1197
|
}
|
|
1198
|
-
function overwriteMerge$1(_a, b) {
|
|
1199
|
-
return b;
|
|
1200
|
-
}
|
|
1201
1198
|
class MetaTable {
|
|
1202
1199
|
elements;
|
|
1203
1200
|
schema;
|
|
@@ -1370,15 +1367,22 @@ class MetaTable {
|
|
|
1370
1367
|
}
|
|
1371
1368
|
}
|
|
1372
1369
|
mergeElement(a, b) {
|
|
1373
|
-
const merged =
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1370
|
+
const merged = { ...a, ...b };
|
|
1371
|
+
const mergedAttrs = {
|
|
1372
|
+
...a.attributes,
|
|
1373
|
+
...b.attributes
|
|
1374
|
+
};
|
|
1375
|
+
for (const [name, attr] of Object.entries(mergedAttrs)) {
|
|
1376
|
+
if (attr.delete) {
|
|
1377
|
+
delete mergedAttrs[name];
|
|
1378
|
+
} else {
|
|
1379
|
+
delete attr.delete;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
merged.attributes = mergedAttrs;
|
|
1383
|
+
if (a.aria) {
|
|
1384
|
+
merged.aria = { ...a.aria, ...b.aria };
|
|
1385
|
+
}
|
|
1382
1386
|
return merged;
|
|
1383
1387
|
}
|
|
1384
1388
|
/**
|
|
@@ -1959,71 +1963,68 @@ function factory(name, context) {
|
|
|
1959
1963
|
function stripslashes(value) {
|
|
1960
1964
|
return value.replaceAll(/\\(.)/g, "$1");
|
|
1961
1965
|
}
|
|
1962
|
-
|
|
1966
|
+
function createClassCondition(classname) {
|
|
1967
|
+
return {
|
|
1968
|
+
kind: "class",
|
|
1969
|
+
classname,
|
|
1970
|
+
match(node) {
|
|
1971
|
+
return node.classList.contains(classname);
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1963
1974
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
}
|
|
1975
|
+
function createIdCondition(raw) {
|
|
1976
|
+
const id = stripslashes(raw);
|
|
1977
|
+
return {
|
|
1978
|
+
kind: "id",
|
|
1979
|
+
id,
|
|
1980
|
+
match(node) {
|
|
1981
|
+
return node.id === id;
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1973
1984
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1985
|
+
function createAttributeCondition(attr) {
|
|
1986
|
+
const match = /^(.+?)(?:([$*^|~]?=)"([^"]+?)")?$/.exec(attr);
|
|
1987
|
+
const key = match[1];
|
|
1988
|
+
const op = match[2];
|
|
1989
|
+
const rawValue = match[3];
|
|
1990
|
+
const value = typeof rawValue === "string" ? stripslashes(rawValue) : rawValue;
|
|
1991
|
+
return {
|
|
1992
|
+
kind: "attribute",
|
|
1993
|
+
key,
|
|
1994
|
+
op,
|
|
1995
|
+
value,
|
|
1996
|
+
match(node) {
|
|
1997
|
+
const attrs = node.getAttribute(key, true);
|
|
1998
|
+
return attrs.some((cur) => {
|
|
1999
|
+
switch (op) {
|
|
2000
|
+
case void 0:
|
|
2001
|
+
return true;
|
|
2002
|
+
/* attribute exists */
|
|
2003
|
+
case "=":
|
|
2004
|
+
return cur.value === value;
|
|
2005
|
+
default:
|
|
2006
|
+
throw new Error(`Attribute selector operator ${op} is not implemented yet`);
|
|
2007
|
+
}
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
1983
2011
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
constructor(attr) {
|
|
1989
|
-
super();
|
|
1990
|
-
const [, key, op, value] = /^(.+?)(?:([$*^|~]?=)"([^"]+?)")?$/.exec(attr);
|
|
1991
|
-
this.key = key;
|
|
1992
|
-
this.op = op;
|
|
1993
|
-
this.value = typeof value === "string" ? stripslashes(value) : value;
|
|
1994
|
-
}
|
|
1995
|
-
match(node) {
|
|
1996
|
-
const attr = node.getAttribute(this.key, true);
|
|
1997
|
-
return attr.some((cur) => {
|
|
1998
|
-
switch (this.op) {
|
|
1999
|
-
case void 0:
|
|
2000
|
-
return true;
|
|
2001
|
-
/* attribute exists */
|
|
2002
|
-
case "=":
|
|
2003
|
-
return cur.value === this.value;
|
|
2004
|
-
default:
|
|
2005
|
-
throw new Error(`Attribute selector operator ${this.op} is not implemented yet`);
|
|
2006
|
-
}
|
|
2007
|
-
});
|
|
2012
|
+
function createPseudoClassCondition(pseudoclass, context) {
|
|
2013
|
+
const match = /^([^(]+)(?:\((.*)\))?$/.exec(pseudoclass);
|
|
2014
|
+
if (!match) {
|
|
2015
|
+
throw new Error(`Missing pseudo-class after colon in selector pattern "${context}"`);
|
|
2008
2016
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2017
|
+
const name = match[1];
|
|
2018
|
+
const args = match[2];
|
|
2019
|
+
return {
|
|
2020
|
+
kind: "pseudo",
|
|
2021
|
+
name,
|
|
2022
|
+
args,
|
|
2023
|
+
match(node, selectorContext) {
|
|
2024
|
+
const fn = factory(name, selectorContext);
|
|
2025
|
+
return fn(node, args);
|
|
2018
2026
|
}
|
|
2019
|
-
|
|
2020
|
-
this.name = name;
|
|
2021
|
-
this.args = args;
|
|
2022
|
-
}
|
|
2023
|
-
match(node, context) {
|
|
2024
|
-
const fn = factory(this.name, context);
|
|
2025
|
-
return fn(node, this.args);
|
|
2026
|
-
}
|
|
2027
|
+
};
|
|
2027
2028
|
}
|
|
2028
2029
|
|
|
2029
2030
|
function isDelimiter(ch) {
|
|
@@ -2098,19 +2099,86 @@ class Compound {
|
|
|
2098
2099
|
createCondition(pattern) {
|
|
2099
2100
|
switch (pattern[0]) {
|
|
2100
2101
|
case ".":
|
|
2101
|
-
return
|
|
2102
|
+
return createClassCondition(pattern.slice(1));
|
|
2102
2103
|
case "#":
|
|
2103
|
-
return
|
|
2104
|
+
return createIdCondition(pattern.slice(1));
|
|
2104
2105
|
case "[":
|
|
2105
|
-
return
|
|
2106
|
+
return createAttributeCondition(pattern.slice(1, -1));
|
|
2106
2107
|
case ":":
|
|
2107
|
-
return
|
|
2108
|
+
return createPseudoClassCondition(pattern.slice(1), this.selector);
|
|
2108
2109
|
default:
|
|
2109
2110
|
throw new Error(`Failed to create selector condition for "${pattern}"`);
|
|
2110
2111
|
}
|
|
2111
2112
|
}
|
|
2112
2113
|
}
|
|
2113
2114
|
|
|
2115
|
+
const escapedCodepoints = /* @__PURE__ */ new Set(["9", "a", "d"]);
|
|
2116
|
+
function* splitSelectorElements(selector) {
|
|
2117
|
+
let begin = 0;
|
|
2118
|
+
let end = 0;
|
|
2119
|
+
function initialState(ch, p) {
|
|
2120
|
+
if (ch === "\\") {
|
|
2121
|
+
return 1 /* ESCAPED */;
|
|
2122
|
+
}
|
|
2123
|
+
if (ch === " ") {
|
|
2124
|
+
end = p;
|
|
2125
|
+
return 2 /* WHITESPACE */;
|
|
2126
|
+
}
|
|
2127
|
+
return 0 /* INITIAL */;
|
|
2128
|
+
}
|
|
2129
|
+
function escapedState(ch) {
|
|
2130
|
+
if (escapedCodepoints.has(ch)) {
|
|
2131
|
+
return 1 /* ESCAPED */;
|
|
2132
|
+
}
|
|
2133
|
+
return 0 /* INITIAL */;
|
|
2134
|
+
}
|
|
2135
|
+
function* whitespaceState(ch, p) {
|
|
2136
|
+
if (ch === " ") {
|
|
2137
|
+
return 2 /* WHITESPACE */;
|
|
2138
|
+
}
|
|
2139
|
+
yield selector.slice(begin, end);
|
|
2140
|
+
begin = p;
|
|
2141
|
+
end = p;
|
|
2142
|
+
return 0 /* INITIAL */;
|
|
2143
|
+
}
|
|
2144
|
+
let state = 0 /* INITIAL */;
|
|
2145
|
+
for (let p = 0; p < selector.length; p++) {
|
|
2146
|
+
const ch = selector[p];
|
|
2147
|
+
switch (state) {
|
|
2148
|
+
case 0 /* INITIAL */:
|
|
2149
|
+
state = initialState(ch, p);
|
|
2150
|
+
break;
|
|
2151
|
+
case 1 /* ESCAPED */:
|
|
2152
|
+
state = escapedState(ch);
|
|
2153
|
+
break;
|
|
2154
|
+
case 2 /* WHITESPACE */:
|
|
2155
|
+
state = yield* whitespaceState(ch, p);
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (begin !== selector.length) {
|
|
2160
|
+
yield selector.slice(begin);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
function unescapeCodepoint(value) {
|
|
2165
|
+
const replacement = {
|
|
2166
|
+
"\\9 ": " ",
|
|
2167
|
+
"\\a ": "\n",
|
|
2168
|
+
"\\d ": "\r"
|
|
2169
|
+
};
|
|
2170
|
+
return value.replaceAll(
|
|
2171
|
+
/(\\[9ad] )/g,
|
|
2172
|
+
(_, codepoint) => replacement[codepoint]
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
function getCompounds(selector) {
|
|
2176
|
+
selector = selector.replaceAll(/([+>~]) /g, "$1");
|
|
2177
|
+
return Array.from(splitSelectorElements(selector), (element) => {
|
|
2178
|
+
return new Compound(unescapeCodepoint(element));
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2114
2182
|
function* ancestors$1(element) {
|
|
2115
2183
|
let current = element.parent;
|
|
2116
2184
|
while (current && !current.isRootElement()) {
|
|
@@ -2173,88 +2241,16 @@ function matchElement(element, compounds, context) {
|
|
|
2173
2241
|
return false;
|
|
2174
2242
|
}
|
|
2175
2243
|
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
function initialState(ch, p) {
|
|
2181
|
-
if (ch === "\\") {
|
|
2182
|
-
return 1 /* ESCAPED */;
|
|
2183
|
-
}
|
|
2184
|
-
if (ch === " ") {
|
|
2185
|
-
end = p;
|
|
2186
|
-
return 2 /* WHITESPACE */;
|
|
2187
|
-
}
|
|
2188
|
-
return 0 /* INITIAL */;
|
|
2189
|
-
}
|
|
2190
|
-
function escapedState(ch) {
|
|
2191
|
-
if (escapedCodepoints.has(ch)) {
|
|
2192
|
-
return 1 /* ESCAPED */;
|
|
2193
|
-
}
|
|
2194
|
-
return 0 /* INITIAL */;
|
|
2195
|
-
}
|
|
2196
|
-
function* whitespaceState(ch, p) {
|
|
2197
|
-
if (ch === " ") {
|
|
2198
|
-
return 2 /* WHITESPACE */;
|
|
2199
|
-
}
|
|
2200
|
-
yield selector.slice(begin, end);
|
|
2201
|
-
begin = p;
|
|
2202
|
-
end = p;
|
|
2203
|
-
return 0 /* INITIAL */;
|
|
2244
|
+
class ComplexSelector {
|
|
2245
|
+
compounds;
|
|
2246
|
+
constructor(compounds) {
|
|
2247
|
+
this.compounds = compounds;
|
|
2204
2248
|
}
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
const ch = selector[p];
|
|
2208
|
-
switch (state) {
|
|
2209
|
-
case 0 /* INITIAL */:
|
|
2210
|
-
state = initialState(ch, p);
|
|
2211
|
-
break;
|
|
2212
|
-
case 1 /* ESCAPED */:
|
|
2213
|
-
state = escapedState(ch);
|
|
2214
|
-
break;
|
|
2215
|
-
case 2 /* WHITESPACE */:
|
|
2216
|
-
state = yield* whitespaceState(ch, p);
|
|
2217
|
-
break;
|
|
2218
|
-
}
|
|
2249
|
+
static fromString(selector) {
|
|
2250
|
+
return new ComplexSelector(getCompounds(selector));
|
|
2219
2251
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
function unescapeCodepoint(value) {
|
|
2226
|
-
const replacement = {
|
|
2227
|
-
"\\9 ": " ",
|
|
2228
|
-
"\\a ": "\n",
|
|
2229
|
-
"\\d ": "\r"
|
|
2230
|
-
};
|
|
2231
|
-
return value.replaceAll(
|
|
2232
|
-
/(\\[9ad] )/g,
|
|
2233
|
-
(_, codepoint) => replacement[codepoint]
|
|
2234
|
-
);
|
|
2235
|
-
}
|
|
2236
|
-
function escapeSelectorComponent(text) {
|
|
2237
|
-
const codepoints = {
|
|
2238
|
-
" ": "\\9 ",
|
|
2239
|
-
"\n": "\\a ",
|
|
2240
|
-
"\r": "\\d "
|
|
2241
|
-
};
|
|
2242
|
-
return text.toString().replaceAll(/([\t\n\r]|[^\w-])/gi, (_, ch) => {
|
|
2243
|
-
if (codepoints[ch]) {
|
|
2244
|
-
return codepoints[ch];
|
|
2245
|
-
} else {
|
|
2246
|
-
return `\\${ch}`;
|
|
2247
|
-
}
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
function generateIdSelector(id) {
|
|
2251
|
-
const escaped = escapeSelectorComponent(id);
|
|
2252
|
-
return /^\d/.test(escaped) ? `[id="${escaped}"]` : `#${escaped}`;
|
|
2253
|
-
}
|
|
2254
|
-
class Selector {
|
|
2255
|
-
pattern;
|
|
2256
|
-
constructor(selector) {
|
|
2257
|
-
this.pattern = Selector.parse(selector);
|
|
2252
|
+
static fromCompounds(compounds) {
|
|
2253
|
+
return new ComplexSelector(compounds);
|
|
2258
2254
|
}
|
|
2259
2255
|
/**
|
|
2260
2256
|
* Match this selector against a HtmlElement.
|
|
@@ -2271,15 +2267,15 @@ class Selector {
|
|
|
2271
2267
|
*/
|
|
2272
2268
|
matchElement(element) {
|
|
2273
2269
|
const context = { scope: null };
|
|
2274
|
-
return matchElement(element, this.
|
|
2270
|
+
return matchElement(element, this.compounds, context);
|
|
2275
2271
|
}
|
|
2276
2272
|
*matchInternal(root, level, context) {
|
|
2277
|
-
if (level >= this.
|
|
2273
|
+
if (level >= this.compounds.length) {
|
|
2278
2274
|
yield root;
|
|
2279
2275
|
return;
|
|
2280
2276
|
}
|
|
2281
|
-
const pattern = this.
|
|
2282
|
-
const matches =
|
|
2277
|
+
const pattern = this.compounds[level];
|
|
2278
|
+
const matches = ComplexSelector.findCandidates(root, pattern);
|
|
2283
2279
|
for (const node of matches) {
|
|
2284
2280
|
if (!pattern.match(node, context)) {
|
|
2285
2281
|
continue;
|
|
@@ -2287,12 +2283,6 @@ class Selector {
|
|
|
2287
2283
|
yield* this.matchInternal(node, level + 1, context);
|
|
2288
2284
|
}
|
|
2289
2285
|
}
|
|
2290
|
-
static parse(selector) {
|
|
2291
|
-
selector = selector.replaceAll(/([+>~]) /g, "$1");
|
|
2292
|
-
return Array.from(splitSelectorElements(selector), (element) => {
|
|
2293
|
-
return new Compound(unescapeCodepoint(element));
|
|
2294
|
-
});
|
|
2295
|
-
}
|
|
2296
2286
|
static findCandidates(root, pattern) {
|
|
2297
2287
|
switch (pattern.combinator) {
|
|
2298
2288
|
case Combinator.DESCENDANT:
|
|
@@ -2300,9 +2290,9 @@ class Selector {
|
|
|
2300
2290
|
case Combinator.CHILD:
|
|
2301
2291
|
return root.childElements.filter((node) => node.is(pattern.tagName));
|
|
2302
2292
|
case Combinator.ADJACENT_SIBLING:
|
|
2303
|
-
return
|
|
2293
|
+
return ComplexSelector.findAdjacentSibling(root);
|
|
2304
2294
|
case Combinator.GENERAL_SIBLING:
|
|
2305
|
-
return
|
|
2295
|
+
return ComplexSelector.findGeneralSibling(root);
|
|
2306
2296
|
case Combinator.SCOPE:
|
|
2307
2297
|
return [root];
|
|
2308
2298
|
}
|
|
@@ -2314,7 +2304,7 @@ class Selector {
|
|
|
2314
2304
|
adjacent = false;
|
|
2315
2305
|
return true;
|
|
2316
2306
|
}
|
|
2317
|
-
if (cur
|
|
2307
|
+
if (cur.isSameNode(node)) {
|
|
2318
2308
|
adjacent = true;
|
|
2319
2309
|
}
|
|
2320
2310
|
return false;
|
|
@@ -2326,7 +2316,7 @@ class Selector {
|
|
|
2326
2316
|
if (after) {
|
|
2327
2317
|
return true;
|
|
2328
2318
|
}
|
|
2329
|
-
if (cur
|
|
2319
|
+
if (cur.isSameNode(node)) {
|
|
2330
2320
|
after = true;
|
|
2331
2321
|
}
|
|
2332
2322
|
return false;
|
|
@@ -2334,6 +2324,31 @@ class Selector {
|
|
|
2334
2324
|
}
|
|
2335
2325
|
}
|
|
2336
2326
|
|
|
2327
|
+
const codepoints = {
|
|
2328
|
+
" ": "\\9 ",
|
|
2329
|
+
"\n": "\\a ",
|
|
2330
|
+
"\r": "\\d "
|
|
2331
|
+
};
|
|
2332
|
+
function escapeSelectorComponent(text) {
|
|
2333
|
+
return text.toString().replaceAll(/([\t\n\r]|[^\w-])/gi, (_, ch) => {
|
|
2334
|
+
if (codepoints[ch]) {
|
|
2335
|
+
return codepoints[ch];
|
|
2336
|
+
} else {
|
|
2337
|
+
return `\\${ch}`;
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
function generateIdSelector(id) {
|
|
2343
|
+
const escaped = escapeSelectorComponent(id);
|
|
2344
|
+
return /^\d/.test(escaped) ? `[id="${escaped}"]` : `#${escaped}`;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
function parseSelector(selector) {
|
|
2348
|
+
const compounds = getCompounds(selector);
|
|
2349
|
+
return ComplexSelector.fromCompounds(compounds);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2337
2352
|
const TEXT_NODE_NAME = "#text";
|
|
2338
2353
|
function isTextNode(node) {
|
|
2339
2354
|
return node?.nodeType === NodeType.TEXT_NODE;
|
|
@@ -2369,6 +2384,7 @@ class TextNode extends DOMNode {
|
|
|
2369
2384
|
}
|
|
2370
2385
|
}
|
|
2371
2386
|
|
|
2387
|
+
const CHILD_ELEMENTS = /* @__PURE__ */ Symbol("childElements");
|
|
2372
2388
|
const ROLE = /* @__PURE__ */ Symbol("role");
|
|
2373
2389
|
const TABINDEX = /* @__PURE__ */ Symbol("tabindex");
|
|
2374
2390
|
var NodeClosed = /* @__PURE__ */ ((NodeClosed2) => {
|
|
@@ -2535,7 +2551,11 @@ class HtmlElement extends DOMNode {
|
|
|
2535
2551
|
* Similar to childNodes but only elements.
|
|
2536
2552
|
*/
|
|
2537
2553
|
get childElements() {
|
|
2538
|
-
|
|
2554
|
+
const cached = this.cacheGet(CHILD_ELEMENTS);
|
|
2555
|
+
if (cached !== void 0) {
|
|
2556
|
+
return cached;
|
|
2557
|
+
}
|
|
2558
|
+
return this.cacheSet(CHILD_ELEMENTS, this.childNodes.filter(isElementNode));
|
|
2539
2559
|
}
|
|
2540
2560
|
/**
|
|
2541
2561
|
* Find the first ancestor matching a selector.
|
|
@@ -2642,7 +2662,7 @@ class HtmlElement extends DOMNode {
|
|
|
2642
2662
|
*/
|
|
2643
2663
|
matches(selectorList) {
|
|
2644
2664
|
return selectorList.split(",").some((it) => {
|
|
2645
|
-
const selector =
|
|
2665
|
+
const selector = parseSelector(it.trim());
|
|
2646
2666
|
return selector.matchElement(this);
|
|
2647
2667
|
});
|
|
2648
2668
|
}
|
|
@@ -2875,9 +2895,9 @@ class HtmlElement extends DOMNode {
|
|
|
2875
2895
|
if (!selectorList) {
|
|
2876
2896
|
return;
|
|
2877
2897
|
}
|
|
2878
|
-
for (const
|
|
2879
|
-
const
|
|
2880
|
-
yield*
|
|
2898
|
+
for (const selectorString of selectorList.split(/(?<!\\),\s*/)) {
|
|
2899
|
+
const selector = parseSelector(selectorString);
|
|
2900
|
+
yield* selector.match(this);
|
|
2881
2901
|
}
|
|
2882
2902
|
}
|
|
2883
2903
|
/**
|
|
@@ -2931,12 +2951,24 @@ class HtmlElement extends DOMNode {
|
|
|
2931
2951
|
}
|
|
2932
2952
|
return visit(this);
|
|
2933
2953
|
}
|
|
2954
|
+
append(node) {
|
|
2955
|
+
super.append(node);
|
|
2956
|
+
this.cacheRemove(CHILD_ELEMENTS);
|
|
2957
|
+
}
|
|
2958
|
+
insertBefore(node, reference) {
|
|
2959
|
+
super.insertBefore(node, reference);
|
|
2960
|
+
this.cacheRemove(CHILD_ELEMENTS);
|
|
2961
|
+
}
|
|
2962
|
+
removeChild(node) {
|
|
2963
|
+
return super.removeChild(node);
|
|
2964
|
+
}
|
|
2934
2965
|
/**
|
|
2935
2966
|
* @internal
|
|
2936
2967
|
*/
|
|
2937
2968
|
_setParent(node) {
|
|
2938
2969
|
const oldParent = this._parent;
|
|
2939
2970
|
this._parent = node instanceof HtmlElement ? node : null;
|
|
2971
|
+
oldParent?.cacheRemove(CHILD_ELEMENTS);
|
|
2940
2972
|
return oldParent;
|
|
2941
2973
|
}
|
|
2942
2974
|
}
|
|
@@ -3718,6 +3750,7 @@ class Rule {
|
|
|
3718
3750
|
severity;
|
|
3719
3751
|
// rule severity
|
|
3720
3752
|
event;
|
|
3753
|
+
tracker;
|
|
3721
3754
|
/**
|
|
3722
3755
|
* Rule name. Defaults to filename without extension but can be overwritten by
|
|
3723
3756
|
* subclasses.
|
|
@@ -3737,6 +3770,7 @@ class Rule {
|
|
|
3737
3770
|
this.blockers = [];
|
|
3738
3771
|
this.severity = Severity.DISABLED;
|
|
3739
3772
|
this.name = "";
|
|
3773
|
+
this.tracker = null;
|
|
3740
3774
|
}
|
|
3741
3775
|
getSeverity() {
|
|
3742
3776
|
return this.severity;
|
|
@@ -3912,7 +3946,15 @@ class Rule {
|
|
|
3912
3946
|
return this.parser.on(event, (_event, data) => {
|
|
3913
3947
|
if (this.isEnabled() && filter(data)) {
|
|
3914
3948
|
this.event = data;
|
|
3915
|
-
|
|
3949
|
+
const { tracker } = this;
|
|
3950
|
+
if (tracker) {
|
|
3951
|
+
const start = performance.now();
|
|
3952
|
+
callback(data);
|
|
3953
|
+
const end = performance.now();
|
|
3954
|
+
tracker.trackRule(this.name, end - start);
|
|
3955
|
+
} else {
|
|
3956
|
+
callback(data);
|
|
3957
|
+
}
|
|
3916
3958
|
}
|
|
3917
3959
|
});
|
|
3918
3960
|
}
|
|
@@ -3929,6 +3971,14 @@ class Rule {
|
|
|
3929
3971
|
this.severity = severity;
|
|
3930
3972
|
this.meta = meta;
|
|
3931
3973
|
}
|
|
3974
|
+
/**
|
|
3975
|
+
* Set (or clear) the performance tracker.
|
|
3976
|
+
*
|
|
3977
|
+
* @internal
|
|
3978
|
+
*/
|
|
3979
|
+
setTracker(tracker) {
|
|
3980
|
+
this.tracker = tracker;
|
|
3981
|
+
}
|
|
3932
3982
|
/**
|
|
3933
3983
|
* Validate rule options against schema. Throws error if object does not validate.
|
|
3934
3984
|
*
|
|
@@ -12443,8 +12493,10 @@ class StaticConfigLoader extends ConfigLoader {
|
|
|
12443
12493
|
|
|
12444
12494
|
class EventHandler {
|
|
12445
12495
|
listeners;
|
|
12496
|
+
tracker;
|
|
12446
12497
|
constructor() {
|
|
12447
12498
|
this.listeners = {};
|
|
12499
|
+
this.tracker = null;
|
|
12448
12500
|
}
|
|
12449
12501
|
/**
|
|
12450
12502
|
* Add an event listener.
|
|
@@ -12483,6 +12535,14 @@ class EventHandler {
|
|
|
12483
12535
|
});
|
|
12484
12536
|
return deregister;
|
|
12485
12537
|
}
|
|
12538
|
+
/**
|
|
12539
|
+
* Set (or clear) the performance tracker.
|
|
12540
|
+
*
|
|
12541
|
+
* @internal
|
|
12542
|
+
*/
|
|
12543
|
+
setTracker(tracker) {
|
|
12544
|
+
this.tracker = tracker;
|
|
12545
|
+
}
|
|
12486
12546
|
/**
|
|
12487
12547
|
* Trigger event causing all listeners to be called.
|
|
12488
12548
|
*
|
|
@@ -12491,8 +12551,18 @@ class EventHandler {
|
|
|
12491
12551
|
*/
|
|
12492
12552
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- technical debt, should be made typesafe */
|
|
12493
12553
|
trigger(event, data) {
|
|
12494
|
-
|
|
12495
|
-
|
|
12554
|
+
const { tracker } = this;
|
|
12555
|
+
if (tracker) {
|
|
12556
|
+
const start = performance.now();
|
|
12557
|
+
for (const listener of this.getCallbacks(event)) {
|
|
12558
|
+
listener.call(null, event, data);
|
|
12559
|
+
}
|
|
12560
|
+
const end = performance.now();
|
|
12561
|
+
tracker.trackEvent(event, end - start);
|
|
12562
|
+
} else {
|
|
12563
|
+
for (const listener of this.getCallbacks(event)) {
|
|
12564
|
+
listener.call(null, event, data);
|
|
12565
|
+
}
|
|
12496
12566
|
}
|
|
12497
12567
|
}
|
|
12498
12568
|
getCallbacks(event) {
|
|
@@ -12504,7 +12574,7 @@ class EventHandler {
|
|
|
12504
12574
|
}
|
|
12505
12575
|
|
|
12506
12576
|
const name = "html-validate";
|
|
12507
|
-
const version = "10.
|
|
12577
|
+
const version = "10.15.0";
|
|
12508
12578
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
12509
12579
|
|
|
12510
12580
|
function freeze(src) {
|
|
@@ -12804,32 +12874,92 @@ class Parser {
|
|
|
12804
12874
|
* valid). The parser handles this by checking if the element on top of the
|
|
12805
12875
|
* stack when is allowed to omit.
|
|
12806
12876
|
*/
|
|
12877
|
+
/**
|
|
12878
|
+
* Check whether a given element would be implicitly closed by an incoming
|
|
12879
|
+
* start tag. Used both in `closeOptional` and in the multi-level lookahead.
|
|
12880
|
+
*/
|
|
12881
|
+
wouldCloseElement(token, element) {
|
|
12882
|
+
if (!element.meta) {
|
|
12883
|
+
return false;
|
|
12884
|
+
}
|
|
12885
|
+
const implicitClosed = element.meta.implicitClosed;
|
|
12886
|
+
if (!implicitClosed) {
|
|
12887
|
+
return false;
|
|
12888
|
+
}
|
|
12889
|
+
const tagName = token.data[2];
|
|
12890
|
+
const incomingMeta = this.metaTable.getMetaFor(tagName);
|
|
12891
|
+
return implicitClosed.some((entry) => {
|
|
12892
|
+
if (!entry.startsWith("@")) {
|
|
12893
|
+
return entry === tagName;
|
|
12894
|
+
}
|
|
12895
|
+
return incomingMeta ? matchesContentCategory(incomingMeta, entry) : false;
|
|
12896
|
+
});
|
|
12897
|
+
}
|
|
12898
|
+
/**
|
|
12899
|
+
* Walk up the active stack to find the parent element that will remain
|
|
12900
|
+
* after all multi-level implicit closes triggered by the incoming start tag.
|
|
12901
|
+
* For a single-level close this is equivalent to `getActive().parent`.
|
|
12902
|
+
*/
|
|
12903
|
+
getParentAfterImplicitClose(token) {
|
|
12904
|
+
let current = this.dom.getActive();
|
|
12905
|
+
while (!current.isRootElement() && this.wouldCloseElement(token, current)) {
|
|
12906
|
+
const { parent } = current;
|
|
12907
|
+
if (!parent) {
|
|
12908
|
+
break;
|
|
12909
|
+
}
|
|
12910
|
+
current = parent;
|
|
12911
|
+
}
|
|
12912
|
+
return current;
|
|
12913
|
+
}
|
|
12807
12914
|
closeOptional(token) {
|
|
12808
12915
|
const active = this.dom.getActive();
|
|
12809
12916
|
if (!active.meta) {
|
|
12810
12917
|
return false;
|
|
12811
12918
|
}
|
|
12812
|
-
const tagName = token.data[2];
|
|
12813
12919
|
const open = !token.data[1];
|
|
12814
|
-
const implicitClosed = active.meta.implicitClosed;
|
|
12815
12920
|
if (open) {
|
|
12816
|
-
|
|
12817
|
-
return false;
|
|
12818
|
-
}
|
|
12819
|
-
const incomingMeta = this.metaTable.getMetaFor(tagName);
|
|
12820
|
-
return implicitClosed.some((entry) => {
|
|
12821
|
-
if (!entry.startsWith("@")) {
|
|
12822
|
-
return entry === tagName;
|
|
12823
|
-
}
|
|
12824
|
-
return incomingMeta ? matchesContentCategory(incomingMeta, entry) : false;
|
|
12825
|
-
});
|
|
12921
|
+
return this.wouldCloseElement(token, active);
|
|
12826
12922
|
} else {
|
|
12827
|
-
|
|
12923
|
+
return this.closeOptionalEndTag(token, active);
|
|
12924
|
+
}
|
|
12925
|
+
}
|
|
12926
|
+
/**
|
|
12927
|
+
* Returns `true` if the element’s end tag may be omitted, either because
|
|
12928
|
+
* its `implicitClosed` list includes its own tag name (e.g. `<li>`, `<td>`)
|
|
12929
|
+
* or because `optionalEnd` is set.
|
|
12930
|
+
*/
|
|
12931
|
+
canOmitEndTag(element) {
|
|
12932
|
+
if (!element.meta) {
|
|
12933
|
+
return false;
|
|
12934
|
+
}
|
|
12935
|
+
const { implicitClosed, optionalEnd } = element.meta;
|
|
12936
|
+
return Boolean(implicitClosed?.includes(element.tagName)) || Boolean(optionalEnd);
|
|
12937
|
+
}
|
|
12938
|
+
/**
|
|
12939
|
+
* Check whether the active element can be implicitly closed by an incoming
|
|
12940
|
+
* end tag. The end tag may close a direct parent or any ancestor, as long as
|
|
12941
|
+
* every intermediate element can also have its end tag omitted.
|
|
12942
|
+
* This handles cases like `</table>` implicitly closing `<td>`, `<tr>`, `<tbody>`.
|
|
12943
|
+
*/
|
|
12944
|
+
closeOptionalEndTag(token, active) {
|
|
12945
|
+
const tagName = token.data[2];
|
|
12946
|
+
if (active.is(tagName)) {
|
|
12947
|
+
return false;
|
|
12948
|
+
}
|
|
12949
|
+
if (!this.canOmitEndTag(active)) {
|
|
12950
|
+
return false;
|
|
12951
|
+
}
|
|
12952
|
+
let ancestor = active.parent;
|
|
12953
|
+
while (ancestor && !ancestor.isRootElement()) {
|
|
12954
|
+
if (ancestor.is(tagName)) {
|
|
12955
|
+
return true;
|
|
12956
|
+
}
|
|
12957
|
+
if (!this.canOmitEndTag(ancestor)) {
|
|
12828
12958
|
return false;
|
|
12829
12959
|
}
|
|
12830
|
-
|
|
12831
|
-
return Boolean(active.parent && active.parent.is(tagName) && canOmitEnd);
|
|
12960
|
+
ancestor = ancestor.parent;
|
|
12832
12961
|
}
|
|
12962
|
+
return false;
|
|
12833
12963
|
}
|
|
12834
12964
|
/**
|
|
12835
12965
|
* Check whether an intermediary element (e.g. `<head>` or `<body>`) should
|
|
@@ -12918,8 +13048,21 @@ class Parser {
|
|
|
12918
13048
|
);
|
|
12919
13049
|
const endToken = tokens.at(-1);
|
|
12920
13050
|
const isStartTag = !startToken.data[1];
|
|
12921
|
-
|
|
12922
|
-
|
|
13051
|
+
let baseParent;
|
|
13052
|
+
if (isStartTag) {
|
|
13053
|
+
baseParent = this.getParentAfterImplicitClose(startToken);
|
|
13054
|
+
} else {
|
|
13055
|
+
const tagName = startToken.data[2];
|
|
13056
|
+
let cur = this.dom.getActive();
|
|
13057
|
+
while (!cur.isRootElement() && !cur.is(tagName)) {
|
|
13058
|
+
const { parent: parent2 } = cur;
|
|
13059
|
+
if (!parent2) {
|
|
13060
|
+
break;
|
|
13061
|
+
}
|
|
13062
|
+
cur = parent2;
|
|
13063
|
+
}
|
|
13064
|
+
baseParent = cur;
|
|
13065
|
+
}
|
|
12923
13066
|
const implicitParent = isStartTag ? this.peekImplicitOpen(startToken, baseParent) : null;
|
|
12924
13067
|
const parent = implicitParent ?? baseParent;
|
|
12925
13068
|
const node = HtmlElement.fromTokens(
|
|
@@ -12931,7 +13074,7 @@ class Parser {
|
|
|
12931
13074
|
);
|
|
12932
13075
|
const isClosing = !isStartTag || node.closed !== NodeClosed.Open;
|
|
12933
13076
|
const isForeign = node.meta?.foreign;
|
|
12934
|
-
|
|
13077
|
+
while (this.closeOptional(startToken)) {
|
|
12935
13078
|
const active = this.dom.getActive();
|
|
12936
13079
|
active.closed = NodeClosed.ImplicitClosed;
|
|
12937
13080
|
this.closeElement(source, node, active, startToken.location);
|
|
@@ -13365,6 +13508,78 @@ class Parser {
|
|
|
13365
13508
|
}
|
|
13366
13509
|
}
|
|
13367
13510
|
|
|
13511
|
+
class PerformanceTracker {
|
|
13512
|
+
eventData;
|
|
13513
|
+
ruleData;
|
|
13514
|
+
startTime;
|
|
13515
|
+
accConfigTime;
|
|
13516
|
+
accTransformTime;
|
|
13517
|
+
constructor() {
|
|
13518
|
+
this.eventData = /* @__PURE__ */ new Map();
|
|
13519
|
+
this.ruleData = /* @__PURE__ */ new Map();
|
|
13520
|
+
this.startTime = performance.now();
|
|
13521
|
+
this.accConfigTime = 0;
|
|
13522
|
+
this.accTransformTime = 0;
|
|
13523
|
+
}
|
|
13524
|
+
/**
|
|
13525
|
+
* Record a single event trigger with the time it took to run all listeners.
|
|
13526
|
+
*/
|
|
13527
|
+
trackEvent(name, time) {
|
|
13528
|
+
const existing = this.eventData.get(name);
|
|
13529
|
+
if (existing) {
|
|
13530
|
+
existing.count += 1;
|
|
13531
|
+
existing.time += time;
|
|
13532
|
+
} else {
|
|
13533
|
+
this.eventData.set(name, { count: 1, time });
|
|
13534
|
+
}
|
|
13535
|
+
}
|
|
13536
|
+
/**
|
|
13537
|
+
* Record time spent loading configuration.
|
|
13538
|
+
*/
|
|
13539
|
+
trackConfig(time) {
|
|
13540
|
+
this.accConfigTime += time;
|
|
13541
|
+
}
|
|
13542
|
+
/**
|
|
13543
|
+
* Record time spent in transformers.
|
|
13544
|
+
*/
|
|
13545
|
+
trackTransform(time) {
|
|
13546
|
+
this.accTransformTime += time;
|
|
13547
|
+
}
|
|
13548
|
+
/**
|
|
13549
|
+
* Record a single rule callback invocation with its execution time.
|
|
13550
|
+
*/
|
|
13551
|
+
trackRule(ruleName, time) {
|
|
13552
|
+
const existing = this.ruleData.get(ruleName);
|
|
13553
|
+
if (existing) {
|
|
13554
|
+
existing.count += 1;
|
|
13555
|
+
existing.time += time;
|
|
13556
|
+
} else {
|
|
13557
|
+
this.ruleData.set(ruleName, { count: 1, time });
|
|
13558
|
+
}
|
|
13559
|
+
}
|
|
13560
|
+
/**
|
|
13561
|
+
* Returns a snapshot of the recorded performance data, with both arrays
|
|
13562
|
+
* sorted by time (descending).
|
|
13563
|
+
*/
|
|
13564
|
+
getResult() {
|
|
13565
|
+
const events = Array.from(
|
|
13566
|
+
this.eventData.entries(),
|
|
13567
|
+
([event, { count, time }]) => ({ event, count, time })
|
|
13568
|
+
).toSorted((a, b) => b.time - a.time);
|
|
13569
|
+
const rules = Array.from(
|
|
13570
|
+
this.ruleData.entries(),
|
|
13571
|
+
([rule, { count, time }]) => ({ rule, count, time })
|
|
13572
|
+
).toSorted((a, b) => b.time - a.time);
|
|
13573
|
+
return {
|
|
13574
|
+
events,
|
|
13575
|
+
rules,
|
|
13576
|
+
configTime: this.accConfigTime,
|
|
13577
|
+
transformTime: this.accTransformTime,
|
|
13578
|
+
totalTime: performance.now() - this.startTime
|
|
13579
|
+
};
|
|
13580
|
+
}
|
|
13581
|
+
}
|
|
13582
|
+
|
|
13368
13583
|
const ruleIds = new Set(Object.keys(bundledRules));
|
|
13369
13584
|
function ruleExists(ruleId) {
|
|
13370
13585
|
return ruleIds.has(ruleId);
|
|
@@ -13412,10 +13627,12 @@ class Engine {
|
|
|
13412
13627
|
config;
|
|
13413
13628
|
ParserClass;
|
|
13414
13629
|
availableRules;
|
|
13415
|
-
|
|
13630
|
+
tracker;
|
|
13631
|
+
constructor(config, ParserClass, options) {
|
|
13416
13632
|
this.report = new Reporter();
|
|
13417
13633
|
this.config = config;
|
|
13418
13634
|
this.ParserClass = ParserClass;
|
|
13635
|
+
this.tracker = options.tracker;
|
|
13419
13636
|
const result = this.initPlugins(this.config);
|
|
13420
13637
|
this.availableRules = {
|
|
13421
13638
|
...bundledRules,
|
|
@@ -13431,6 +13648,9 @@ class Engine {
|
|
|
13431
13648
|
lint(sources) {
|
|
13432
13649
|
for (const source of sources) {
|
|
13433
13650
|
const parser = this.instantiateParser();
|
|
13651
|
+
if (this.tracker) {
|
|
13652
|
+
parser.getEventHandler().setTracker(this.tracker);
|
|
13653
|
+
}
|
|
13434
13654
|
const { rules } = this.setupPlugins(source, this.config, parser);
|
|
13435
13655
|
const noUnusedDisable = rules["no-unused-disable"];
|
|
13436
13656
|
const directiveContext = {
|
|
@@ -13707,6 +13927,7 @@ class Engine {
|
|
|
13707
13927
|
const rule = this.instantiateRule(ruleId, options);
|
|
13708
13928
|
rule.name = ruleId;
|
|
13709
13929
|
rule.init(parser, report, severity, meta);
|
|
13930
|
+
rule.setTracker(this.tracker);
|
|
13710
13931
|
if (rule.setup) {
|
|
13711
13932
|
rule.setup();
|
|
13712
13933
|
}
|
|
@@ -15065,5 +15286,5 @@ const engines = {
|
|
|
15065
15286
|
|
|
15066
15287
|
var workerPath = "./jest-worker.js";
|
|
15067
15288
|
|
|
15068
|
-
export {
|
|
15289
|
+
export { walk as $, Attribute as A, Severity as B, ConfigLoader as C, DOMNode as D, Engine as E, TextContent$1 as F, TextNode as G, HtmlElement as H, ariaNaming as I, classifyNodeText as J, presets as K, defineConfig as L, MetaCopyableProperty as M, NestedError as N, definePlugin as O, PerformanceTracker as P, isUserError as Q, Reporter as R, StaticConfigLoader as S, TextClassification as T, UserError as U, Validator as V, WrappedError as W, keywordPatternMatcher as X, ruleExists as Y, sliceLocation as Z, staticResolver as _, Parser as a, engines as a0, codeFrameColumns as a1, getEndLocation as a2, getStartLocation as a3, workerPath as a4, name as a5, bugs as a6, transformSourceSync as b, transformFilename as c, transformFilenameSync as d, configurationSchema as e, ConfigError as f, Config as g, compatibilityCheckImpl as h, isThenable as i, ensureError as j, getFormatter as k, deepmerge as l, ignore as m, normalizeSource as n, DOMTokenList as o, DOMTree as p, DynamicValue as q, EventHandler as r, MetaTable as s, transformSource as t, NodeClosed as u, version as v, NodeType as w, ResolvedConfig as x, Rule as y, SchemaValidationError as z };
|
|
15069
15290
|
//# sourceMappingURL=core.js.map
|