@markuplint/selector 3.0.0-alpha.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/LICENSE +21 -0
- package/README.md +112 -0
- package/lib/compare-specificity.d.ts +2 -0
- package/lib/compare-specificity.js +25 -0
- package/lib/create-selector.d.ts +3 -0
- package/lib/create-selector.js +20 -0
- package/lib/debug.d.ts +3 -0
- package/lib/debug.js +18 -0
- package/lib/extended-selector/aria-pseudo-class.d.ts +5 -0
- package/lib/extended-selector/aria-pseudo-class.js +49 -0
- package/lib/extended-selector/aria-role-pseudo-class.d.ts +3 -0
- package/lib/extended-selector/aria-role-pseudo-class.js +24 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +11 -0
- package/lib/invalid-selector-error.d.ts +3 -0
- package/lib/invalid-selector-error.js +10 -0
- package/lib/is-pure-html-element.d.ts +1 -0
- package/lib/is-pure-html-element.js +7 -0
- package/lib/is.d.ts +3 -0
- package/lib/is.js +15 -0
- package/lib/match-selector.d.ts +13 -0
- package/lib/match-selector.js +238 -0
- package/lib/regex-selector-matches.d.ts +3 -0
- package/lib/regex-selector-matches.js +25 -0
- package/lib/selector.d.ts +8 -0
- package/lib/selector.js +647 -0
- package/lib/types.d.ts +16 -0
- package/lib/types.js +2 -0
- package/lib/utils/is-pure-html-element.d.ts +1 -0
- package/lib/utils/is-pure-html-element.js +7 -0
- package/package.json +31 -0
- package/tsconfig.test.json +3 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SelectorResult, Specificity } from './types';
|
|
2
|
+
declare type ExtendedPseudoClass = Record<string, (content: string) => (el: Element) => SelectorResult>;
|
|
3
|
+
export declare class Selector {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(selector: string, extended?: ExtendedPseudoClass);
|
|
6
|
+
match(el: Node, scope?: ParentNode | null): Specificity | false;
|
|
7
|
+
}
|
|
8
|
+
export {};
|
package/lib/selector.js
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _Selector_ruleset, _Ruleset_selectorGroup, _StructuredSelector_selector, _StructuredSelector_edge, _SelectorTarget_extended, _SelectorTarget_combinatedFrom, _SelectorTarget_isAdded;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Selector = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
7
|
+
const postcss_selector_parser_1 = tslib_1.__importStar(require("postcss-selector-parser"));
|
|
8
|
+
const compare_specificity_1 = require("./compare-specificity");
|
|
9
|
+
const debug_1 = require("./debug");
|
|
10
|
+
const invalid_selector_error_1 = require("./invalid-selector-error");
|
|
11
|
+
const is_1 = require("./is");
|
|
12
|
+
const selLog = debug_1.log.extend('selector');
|
|
13
|
+
const resLog = debug_1.log.extend('result');
|
|
14
|
+
class Selector {
|
|
15
|
+
constructor(selector, extended = {}) {
|
|
16
|
+
_Selector_ruleset.set(this, void 0);
|
|
17
|
+
tslib_1.__classPrivateFieldSet(this, _Selector_ruleset, Ruleset.parse(selector, extended), "f");
|
|
18
|
+
}
|
|
19
|
+
match(el, scope = (0, is_1.isElement)(el) ? el : null) {
|
|
20
|
+
const results = tslib_1.__classPrivateFieldGet(this, _Selector_ruleset, "f").match(el, scope);
|
|
21
|
+
for (const result of results) {
|
|
22
|
+
if (result.matched) {
|
|
23
|
+
return result.specificity;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.Selector = Selector;
|
|
30
|
+
_Selector_ruleset = new WeakMap();
|
|
31
|
+
class Ruleset {
|
|
32
|
+
constructor(selectors, extended, depth) {
|
|
33
|
+
_Ruleset_selectorGroup.set(this, []);
|
|
34
|
+
tslib_1.__classPrivateFieldGet(this, _Ruleset_selectorGroup, "f").push(...selectors.map(selector => new StructuredSelector(selector, depth, extended)));
|
|
35
|
+
const head = tslib_1.__classPrivateFieldGet(this, _Ruleset_selectorGroup, "f")[0];
|
|
36
|
+
this.headCombinator = (head === null || head === void 0 ? void 0 : head.headCombinator) || null;
|
|
37
|
+
if (this.headCombinator) {
|
|
38
|
+
if (depth <= 0) {
|
|
39
|
+
throw new invalid_selector_error_1.InvalidSelectorError(`'${tslib_1.__classPrivateFieldGet(this, _Ruleset_selectorGroup, "f")[0].selector}' is not a valid selector`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
static parse(selector, extended) {
|
|
44
|
+
const selectors = [];
|
|
45
|
+
try {
|
|
46
|
+
(0, postcss_selector_parser_1.default)(root => {
|
|
47
|
+
selectors.push(...root.nodes);
|
|
48
|
+
}).processSync(selector);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
if (e instanceof Error) {
|
|
52
|
+
throw new Error(`${e.message} At the selector: "${selector}"`);
|
|
53
|
+
}
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
return new Ruleset(selectors, extended, 0);
|
|
57
|
+
}
|
|
58
|
+
match(el, scope) {
|
|
59
|
+
(0, debug_1.log)('<%s> (%s)', (0, is_1.isElement)(el) ? el.localName : el.nodeName, scope ? ((0, is_1.isElement)(scope) ? scope.localName : scope.nodeName) : null);
|
|
60
|
+
return tslib_1.__classPrivateFieldGet(this, _Ruleset_selectorGroup, "f").map(selector => {
|
|
61
|
+
selLog('"%s"', selector.selector);
|
|
62
|
+
const res = selector.match(el, scope);
|
|
63
|
+
resLog('%s "%s" => %o', (0, is_1.isElement)(el) ? el.localName : el.nodeName, selector.selector, res);
|
|
64
|
+
return res;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
_Ruleset_selectorGroup = new WeakMap();
|
|
69
|
+
class StructuredSelector {
|
|
70
|
+
constructor(selector, depth, extended) {
|
|
71
|
+
_StructuredSelector_selector.set(this, void 0);
|
|
72
|
+
_StructuredSelector_edge.set(this, void 0);
|
|
73
|
+
tslib_1.__classPrivateFieldSet(this, _StructuredSelector_selector, selector, "f");
|
|
74
|
+
tslib_1.__classPrivateFieldSet(this, _StructuredSelector_edge, new SelectorTarget(extended, depth), "f");
|
|
75
|
+
this.headCombinator =
|
|
76
|
+
tslib_1.__classPrivateFieldGet(this, _StructuredSelector_selector, "f").nodes[0].type === 'combinator' ? tslib_1.__classPrivateFieldGet(this, _StructuredSelector_selector, "f").nodes[0].value || null : null;
|
|
77
|
+
if (0 < depth && this.headCombinator) {
|
|
78
|
+
tslib_1.__classPrivateFieldGet(this, _StructuredSelector_selector, "f").nodes.unshift((0, postcss_selector_parser_1.pseudo)({ value: ':scope' }));
|
|
79
|
+
}
|
|
80
|
+
tslib_1.__classPrivateFieldGet(this, _StructuredSelector_selector, "f").nodes.forEach(node => {
|
|
81
|
+
switch (node.type) {
|
|
82
|
+
case 'combinator': {
|
|
83
|
+
const combinatedTarget = new SelectorTarget(extended, depth);
|
|
84
|
+
combinatedTarget.from(tslib_1.__classPrivateFieldGet(this, _StructuredSelector_edge, "f"), node);
|
|
85
|
+
tslib_1.__classPrivateFieldSet(this, _StructuredSelector_edge, combinatedTarget, "f");
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'root':
|
|
89
|
+
case 'string': {
|
|
90
|
+
throw new Error(`Unsupported selector: ${selector.toString()}`);
|
|
91
|
+
}
|
|
92
|
+
case 'nesting': {
|
|
93
|
+
throw new Error(`Unsupported nested selector: ${selector.toString()}`);
|
|
94
|
+
}
|
|
95
|
+
case 'comment': {
|
|
96
|
+
throw new Error(`Unsupported comment in selector: ${selector.toString()}`);
|
|
97
|
+
}
|
|
98
|
+
default: {
|
|
99
|
+
tslib_1.__classPrivateFieldGet(this, _StructuredSelector_edge, "f").add(node);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
get selector() {
|
|
105
|
+
return tslib_1.__classPrivateFieldGet(this, _StructuredSelector_selector, "f").nodes.join('');
|
|
106
|
+
}
|
|
107
|
+
match(el, scope) {
|
|
108
|
+
return tslib_1.__classPrivateFieldGet(this, _StructuredSelector_edge, "f").match(el, scope, 0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
_StructuredSelector_selector = new WeakMap(), _StructuredSelector_edge = new WeakMap();
|
|
112
|
+
class SelectorTarget {
|
|
113
|
+
constructor(extended, depth) {
|
|
114
|
+
_SelectorTarget_extended.set(this, void 0);
|
|
115
|
+
_SelectorTarget_combinatedFrom.set(this, null);
|
|
116
|
+
_SelectorTarget_isAdded.set(this, false);
|
|
117
|
+
this.attr = [];
|
|
118
|
+
this.class = [];
|
|
119
|
+
this.id = [];
|
|
120
|
+
this.pseudo = [];
|
|
121
|
+
this.tag = null;
|
|
122
|
+
tslib_1.__classPrivateFieldSet(this, _SelectorTarget_extended, extended, "f");
|
|
123
|
+
this.depth = depth;
|
|
124
|
+
}
|
|
125
|
+
add(selector) {
|
|
126
|
+
tslib_1.__classPrivateFieldSet(this, _SelectorTarget_isAdded, true, "f");
|
|
127
|
+
switch (selector.type) {
|
|
128
|
+
case 'tag':
|
|
129
|
+
case 'universal': {
|
|
130
|
+
this.tag = selector;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'id': {
|
|
134
|
+
this.id.push(selector);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 'class': {
|
|
138
|
+
this.class.push(selector);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case 'attribute': {
|
|
142
|
+
this.attr.push(selector);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'pseudo': {
|
|
146
|
+
this.pseudo.push(selector);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
from(target, combinator) {
|
|
152
|
+
tslib_1.__classPrivateFieldSet(this, _SelectorTarget_combinatedFrom, { target, combinator }, "f");
|
|
153
|
+
}
|
|
154
|
+
match(el, scope, count) {
|
|
155
|
+
var _a;
|
|
156
|
+
const result = this._match(el, scope, count);
|
|
157
|
+
if (selLog.enabled) {
|
|
158
|
+
const nodeName = el.nodeName;
|
|
159
|
+
const selector = ((_a = tslib_1.__classPrivateFieldGet(this, _SelectorTarget_combinatedFrom, "f")) === null || _a === void 0 ? void 0 : _a.target.toString()) || this.toString();
|
|
160
|
+
const combinator = result.combinator ? ` ${result.combinator}` : '';
|
|
161
|
+
selLog('The %s element by "%s" => %s (%d)', nodeName, `${selector}${combinator}`, result.matched, count);
|
|
162
|
+
if (selector === ':scope') {
|
|
163
|
+
selLog(`† Scope is the ${(scope === null || scope === void 0 ? void 0 : scope.nodeName) || null}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
delete result.combinator;
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
_match(el, scope, count) {
|
|
170
|
+
const unitCheck = this._matchWithoutCombinateChecking(el, scope);
|
|
171
|
+
if (!unitCheck.matched) {
|
|
172
|
+
return unitCheck;
|
|
173
|
+
}
|
|
174
|
+
if (!tslib_1.__classPrivateFieldGet(this, _SelectorTarget_combinatedFrom, "f")) {
|
|
175
|
+
return unitCheck;
|
|
176
|
+
}
|
|
177
|
+
if (!(0, is_1.isNonDocumentTypeChildNode)(el)) {
|
|
178
|
+
return unitCheck;
|
|
179
|
+
}
|
|
180
|
+
const { target, combinator } = tslib_1.__classPrivateFieldGet(this, _SelectorTarget_combinatedFrom, "f");
|
|
181
|
+
switch (combinator.value) {
|
|
182
|
+
// Descendant combinator
|
|
183
|
+
case ' ': {
|
|
184
|
+
let ancestor = el.parentElement;
|
|
185
|
+
let matched = false;
|
|
186
|
+
let specificity;
|
|
187
|
+
while (ancestor) {
|
|
188
|
+
const res = target.match(ancestor, scope, count + 1);
|
|
189
|
+
if (!specificity) {
|
|
190
|
+
specificity = [
|
|
191
|
+
unitCheck.specificity[0] + res.specificity[0],
|
|
192
|
+
unitCheck.specificity[1] + res.specificity[1],
|
|
193
|
+
unitCheck.specificity[2] + res.specificity[2],
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
if (res.matched) {
|
|
197
|
+
matched = true;
|
|
198
|
+
}
|
|
199
|
+
ancestor = ancestor.parentElement;
|
|
200
|
+
}
|
|
201
|
+
if (!specificity) {
|
|
202
|
+
const res = target.match(el, scope, count + 1);
|
|
203
|
+
specificity = [
|
|
204
|
+
unitCheck.specificity[0] + res.specificity[0],
|
|
205
|
+
unitCheck.specificity[1] + res.specificity[1],
|
|
206
|
+
unitCheck.specificity[2] + res.specificity[2],
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
combinator: '␣',
|
|
211
|
+
specificity,
|
|
212
|
+
matched,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Child combinator
|
|
216
|
+
case '>': {
|
|
217
|
+
let matched;
|
|
218
|
+
const specificity = unitCheck.specificity;
|
|
219
|
+
const parentNode = el.parentElement;
|
|
220
|
+
if (parentNode) {
|
|
221
|
+
const res = target.match(parentNode, scope, count + 1);
|
|
222
|
+
specificity[0] += res.specificity[0];
|
|
223
|
+
specificity[1] += res.specificity[1];
|
|
224
|
+
specificity[2] += res.specificity[2];
|
|
225
|
+
matched = res.matched;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const res = target.match(el, scope, count + 1);
|
|
229
|
+
specificity[0] += res.specificity[0];
|
|
230
|
+
specificity[1] += res.specificity[1];
|
|
231
|
+
specificity[2] += res.specificity[2];
|
|
232
|
+
matched = false;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
combinator: '>',
|
|
236
|
+
specificity,
|
|
237
|
+
matched,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// Next-sibling combinator
|
|
241
|
+
case '+': {
|
|
242
|
+
let matched;
|
|
243
|
+
const specificity = unitCheck.specificity;
|
|
244
|
+
if (el.previousElementSibling) {
|
|
245
|
+
const res = target.match(el.previousElementSibling, scope, count + 1);
|
|
246
|
+
specificity[0] += res.specificity[0];
|
|
247
|
+
specificity[1] += res.specificity[1];
|
|
248
|
+
specificity[2] += res.specificity[2];
|
|
249
|
+
matched = res.matched;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const res = target.match(el, scope, count + 1);
|
|
253
|
+
specificity[0] += res.specificity[0];
|
|
254
|
+
specificity[1] += res.specificity[1];
|
|
255
|
+
specificity[2] += res.specificity[2];
|
|
256
|
+
matched = false;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
combinator: '+',
|
|
260
|
+
specificity,
|
|
261
|
+
matched,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// Subsequent-sibling combinator
|
|
265
|
+
case '~': {
|
|
266
|
+
let prev = el.previousElementSibling;
|
|
267
|
+
let matched = false;
|
|
268
|
+
let specificity;
|
|
269
|
+
while (prev) {
|
|
270
|
+
const res = target.match(prev, scope, count + 1);
|
|
271
|
+
if (!specificity) {
|
|
272
|
+
specificity = [
|
|
273
|
+
unitCheck.specificity[0] + res.specificity[0],
|
|
274
|
+
unitCheck.specificity[1] + res.specificity[1],
|
|
275
|
+
unitCheck.specificity[2] + res.specificity[2],
|
|
276
|
+
];
|
|
277
|
+
}
|
|
278
|
+
if (res.matched) {
|
|
279
|
+
matched = true;
|
|
280
|
+
}
|
|
281
|
+
prev = prev.previousElementSibling;
|
|
282
|
+
}
|
|
283
|
+
if (!specificity) {
|
|
284
|
+
const res = target.match(el, scope, count + 1);
|
|
285
|
+
specificity = [
|
|
286
|
+
unitCheck.specificity[0] + res.specificity[0],
|
|
287
|
+
unitCheck.specificity[1] + res.specificity[1],
|
|
288
|
+
unitCheck.specificity[2] + res.specificity[2],
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
combinator: '~',
|
|
293
|
+
specificity,
|
|
294
|
+
matched,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
// Column combinator
|
|
298
|
+
case '||': {
|
|
299
|
+
throw new Error('Unsupported column combinator yet. If you want it, please request it as the issue (https://github.com/markuplint/markuplint/issues/new).');
|
|
300
|
+
}
|
|
301
|
+
default: {
|
|
302
|
+
throw new Error(`Unsupported ${tslib_1.__classPrivateFieldGet(this, _SelectorTarget_combinatedFrom, "f").combinator.value} combinator in selector`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
toString() {
|
|
307
|
+
var _a, _b;
|
|
308
|
+
return [
|
|
309
|
+
(_b = (_a = this.tag) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '',
|
|
310
|
+
this.id.map(id => `#${id.value}`).join(''),
|
|
311
|
+
this.class.map(c => `.${c.value}`).join(''),
|
|
312
|
+
this.attr.map(attr => `[${attr.toString()}]`).join(''),
|
|
313
|
+
this.pseudo.map(pseudo => pseudo.value).join(''),
|
|
314
|
+
].join('');
|
|
315
|
+
}
|
|
316
|
+
_matchWithoutCombinateChecking(el, scope) {
|
|
317
|
+
const specificity = [0, 0, 0];
|
|
318
|
+
if (!(0, is_1.isElement)(el)) {
|
|
319
|
+
return {
|
|
320
|
+
specificity,
|
|
321
|
+
matched: false,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
// @ts-ignore
|
|
325
|
+
if (this.tag && this.tag._namespace) {
|
|
326
|
+
// @ts-ignore
|
|
327
|
+
const namespace = `${this.tag._namespace}`.toLowerCase();
|
|
328
|
+
switch (namespace) {
|
|
329
|
+
case '*':
|
|
330
|
+
case 'true': {
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
case 'svg': {
|
|
334
|
+
if (el.namespaceURI !== 'http://www.w3.org/2000/svg') {
|
|
335
|
+
return {
|
|
336
|
+
specificity,
|
|
337
|
+
matched: false,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
default: {
|
|
343
|
+
throw new invalid_selector_error_1.InvalidSelectorError(`The ${namespace} namespace is not supported`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
let matched = true;
|
|
348
|
+
if (!tslib_1.__classPrivateFieldGet(this, _SelectorTarget_isAdded, "f") && !isScope(el, scope)) {
|
|
349
|
+
matched = false;
|
|
350
|
+
}
|
|
351
|
+
if (!this.id.every(id => id.value === el.id)) {
|
|
352
|
+
matched = false;
|
|
353
|
+
}
|
|
354
|
+
specificity[0] += this.id.length;
|
|
355
|
+
if (!this.class.every(className => el.classList.contains(className.value))) {
|
|
356
|
+
matched = false;
|
|
357
|
+
}
|
|
358
|
+
specificity[1] += this.class.length;
|
|
359
|
+
if (!this.attr.every(attr => attrMatch(attr, el))) {
|
|
360
|
+
matched = false;
|
|
361
|
+
}
|
|
362
|
+
specificity[1] += this.attr.length;
|
|
363
|
+
for (const pseudo of this.pseudo) {
|
|
364
|
+
const pseudoRes = pseudoMatch(pseudo, el, scope, tslib_1.__classPrivateFieldGet(this, _SelectorTarget_extended, "f"), this.depth);
|
|
365
|
+
specificity[0] += pseudoRes.specificity[0];
|
|
366
|
+
specificity[1] += pseudoRes.specificity[1];
|
|
367
|
+
specificity[2] += pseudoRes.specificity[2];
|
|
368
|
+
if (!pseudoRes.matched) {
|
|
369
|
+
matched = false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (this.tag && this.tag.type === 'tag') {
|
|
373
|
+
specificity[2] += 1;
|
|
374
|
+
let a = this.tag.value;
|
|
375
|
+
let b = el.localName;
|
|
376
|
+
if ((0, is_1.isPureHTMLElement)(el)) {
|
|
377
|
+
a = a.toLowerCase();
|
|
378
|
+
b = b.toLowerCase();
|
|
379
|
+
}
|
|
380
|
+
if (a !== b) {
|
|
381
|
+
matched = false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
specificity,
|
|
386
|
+
matched,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
_SelectorTarget_extended = new WeakMap(), _SelectorTarget_combinatedFrom = new WeakMap(), _SelectorTarget_isAdded = new WeakMap();
|
|
391
|
+
function attrMatch(attr, el) {
|
|
392
|
+
return Array.from(el.attributes).some(attrOfEl => {
|
|
393
|
+
if (attr.attribute !== attrOfEl.localName) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (attr.namespace != null && attr.namespace !== true && attr.namespace !== '*') {
|
|
397
|
+
const ns = (0, ml_spec_1.resolveNamespace)(attrOfEl.localName, attrOfEl.namespaceURI);
|
|
398
|
+
if (attr.namespace !== ns.namespace) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (attr.value != null) {
|
|
403
|
+
let value = attr.value;
|
|
404
|
+
let valueOfEl = attrOfEl.value;
|
|
405
|
+
if (attr.insensitive) {
|
|
406
|
+
value = value.toLowerCase();
|
|
407
|
+
valueOfEl = valueOfEl.toLowerCase();
|
|
408
|
+
}
|
|
409
|
+
switch (attr.operator) {
|
|
410
|
+
case '=': {
|
|
411
|
+
if (value !== valueOfEl) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case '~=': {
|
|
417
|
+
if (!valueOfEl.split(/\s+/).includes(value)) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case '|=': {
|
|
423
|
+
if (!new RegExp(`^${value}(?:$|-)`).test(valueOfEl)) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case '*=': {
|
|
429
|
+
if (valueOfEl.indexOf(value) === -1) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
case '^=': {
|
|
435
|
+
if (valueOfEl.indexOf(value) !== 0) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
case '$=': {
|
|
441
|
+
if (valueOfEl.lastIndexOf(value) !== valueOfEl.length - value.length) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return true;
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function pseudoMatch(pseudo, el, scope, extended, depth) {
|
|
452
|
+
switch (pseudo.value) {
|
|
453
|
+
//
|
|
454
|
+
/**
|
|
455
|
+
* Below, markuplint Specific Selector
|
|
456
|
+
*/
|
|
457
|
+
case ':closest': {
|
|
458
|
+
const ruleset = new Ruleset(pseudo.nodes, extended, depth + 1);
|
|
459
|
+
const specificity = getSpecificity(ruleset.match(el, scope));
|
|
460
|
+
let parent = el.parentElement;
|
|
461
|
+
while (parent) {
|
|
462
|
+
if (ruleset.match(parent, scope).some(r => r.matched)) {
|
|
463
|
+
return {
|
|
464
|
+
specificity,
|
|
465
|
+
matched: true,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
parent = parent.parentElement;
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
specificity,
|
|
472
|
+
matched: false,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Below, Selector Level 4
|
|
477
|
+
*/
|
|
478
|
+
case ':not': {
|
|
479
|
+
const ruleset = new Ruleset(pseudo.nodes, extended, depth + 1);
|
|
480
|
+
const resList = ruleset.match(el, scope);
|
|
481
|
+
const specificity = getSpecificity(resList);
|
|
482
|
+
const matched = resList.every(r => !r.matched);
|
|
483
|
+
return {
|
|
484
|
+
specificity,
|
|
485
|
+
matched,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
case ':is': {
|
|
489
|
+
const ruleset = new Ruleset(pseudo.nodes, extended, depth + 1);
|
|
490
|
+
const resList = ruleset.match(el, scope);
|
|
491
|
+
const specificity = getSpecificity(resList);
|
|
492
|
+
const matched = resList.some(r => r.matched);
|
|
493
|
+
return {
|
|
494
|
+
specificity,
|
|
495
|
+
matched,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
case ':has': {
|
|
499
|
+
const ruleset = new Ruleset(pseudo.nodes, extended, depth + 1);
|
|
500
|
+
const specificity = getSpecificity(ruleset.match(el, scope));
|
|
501
|
+
switch (ruleset.headCombinator) {
|
|
502
|
+
case '+':
|
|
503
|
+
case '~': {
|
|
504
|
+
const matched = getSiblings(el).some(sib => ruleset.match(sib, el).some(m => m.matched));
|
|
505
|
+
return {
|
|
506
|
+
specificity,
|
|
507
|
+
matched,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
default: {
|
|
511
|
+
const matched = getDescendants(el).some(desc => ruleset.match(desc, el).some(m => m.matched));
|
|
512
|
+
return {
|
|
513
|
+
specificity,
|
|
514
|
+
matched,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
case ':where': {
|
|
520
|
+
const ruleset = new Ruleset(pseudo.nodes, extended, depth + 1);
|
|
521
|
+
const resList = ruleset.match(el, scope);
|
|
522
|
+
const matched = resList.some(r => r.matched);
|
|
523
|
+
return {
|
|
524
|
+
specificity: [0, 0, 0],
|
|
525
|
+
matched,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
case ':scope': {
|
|
529
|
+
if (isScope(el, scope)) {
|
|
530
|
+
return {
|
|
531
|
+
specificity: [0, 1, 0],
|
|
532
|
+
matched: true,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
specificity: [0, 1, 0],
|
|
537
|
+
matched: false,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
case ':root': {
|
|
541
|
+
if (el.localName === 'html') {
|
|
542
|
+
return {
|
|
543
|
+
specificity: [0, 1, 0],
|
|
544
|
+
matched: true,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
specificity: [0, 1, 0],
|
|
549
|
+
matched: false,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
case ':enable':
|
|
553
|
+
case ':disable':
|
|
554
|
+
case ':read-write':
|
|
555
|
+
case ':read-only':
|
|
556
|
+
case ':placeholder-shown':
|
|
557
|
+
case ':default':
|
|
558
|
+
case ':checked':
|
|
559
|
+
case ':indeterminate':
|
|
560
|
+
case ':valid':
|
|
561
|
+
case ':invalid':
|
|
562
|
+
case ':in-range':
|
|
563
|
+
case ':out-of-range':
|
|
564
|
+
case ':required':
|
|
565
|
+
case ':optional':
|
|
566
|
+
case ':blank':
|
|
567
|
+
case ':user-invalid':
|
|
568
|
+
case ':empty':
|
|
569
|
+
case ':nth-child':
|
|
570
|
+
case ':nth-last-child':
|
|
571
|
+
case ':first-child':
|
|
572
|
+
case ':last-child':
|
|
573
|
+
case ':only-child':
|
|
574
|
+
case ':nth-of-type':
|
|
575
|
+
case ':nth-last-of-type':
|
|
576
|
+
case ':first-of-type':
|
|
577
|
+
case ':last-of-type':
|
|
578
|
+
case ':only-of-type':
|
|
579
|
+
case ':nth-last-col':
|
|
580
|
+
case ':nth-col': {
|
|
581
|
+
throw new Error(`Unsupported pseudo ${pseudo.toString()} selector yet. If you want it, please request it as the issue (https://github.com/markuplint/markuplint/issues/new).`);
|
|
582
|
+
}
|
|
583
|
+
case ':dir':
|
|
584
|
+
case ':lang':
|
|
585
|
+
case ':any-link':
|
|
586
|
+
case ':link':
|
|
587
|
+
case ':visited':
|
|
588
|
+
case ':local-link':
|
|
589
|
+
case ':target':
|
|
590
|
+
case ':target-within':
|
|
591
|
+
case ':current':
|
|
592
|
+
case ':past':
|
|
593
|
+
case ':future':
|
|
594
|
+
case ':active':
|
|
595
|
+
case ':hover':
|
|
596
|
+
case ':focus':
|
|
597
|
+
case ':focus-within':
|
|
598
|
+
case ':focus-visible':
|
|
599
|
+
case '::before':
|
|
600
|
+
case '::after':
|
|
601
|
+
default: {
|
|
602
|
+
for (const ext of Object.keys(extended)) {
|
|
603
|
+
if (pseudo.value !== `:${ext}`) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const content = pseudo.nodes.map(node => node.toString()).join('');
|
|
607
|
+
const hook = extended[ext];
|
|
608
|
+
const matcher = hook(content);
|
|
609
|
+
return matcher(el);
|
|
610
|
+
}
|
|
611
|
+
throw new Error(`Unsupported pseudo ${pseudo.toString()} selector.`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function isScope(el, scope) {
|
|
616
|
+
return el === scope || el.parentNode === null;
|
|
617
|
+
}
|
|
618
|
+
function getDescendants(el, includeSelf = false) {
|
|
619
|
+
return [
|
|
620
|
+
...Array.from(el.children)
|
|
621
|
+
.map(child => getDescendants(child, true))
|
|
622
|
+
.flat(),
|
|
623
|
+
...(includeSelf ? [el] : []),
|
|
624
|
+
];
|
|
625
|
+
}
|
|
626
|
+
function getSiblings(el) {
|
|
627
|
+
var _a;
|
|
628
|
+
return Array.from(((_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.children) || []);
|
|
629
|
+
}
|
|
630
|
+
function getSpecificity(result) {
|
|
631
|
+
let specificity;
|
|
632
|
+
for (const res of result) {
|
|
633
|
+
if (specificity) {
|
|
634
|
+
const order = (0, compare_specificity_1.compareSpecificity)(specificity, res.specificity);
|
|
635
|
+
if (order === -1) {
|
|
636
|
+
specificity = res.specificity;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
specificity = res.specificity;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (!specificity) {
|
|
644
|
+
throw new Error('Result is empty');
|
|
645
|
+
}
|
|
646
|
+
return specificity;
|
|
647
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare type Specificity = [number, number, number];
|
|
2
|
+
export declare type SelectorResult = {
|
|
3
|
+
specificity: Specificity;
|
|
4
|
+
matched: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare type RegexSelector = RegexSelectorWithoutCompination & {
|
|
7
|
+
combination?: {
|
|
8
|
+
combinator: RegexSelectorCombinator;
|
|
9
|
+
} & RegexSelector;
|
|
10
|
+
};
|
|
11
|
+
export declare type RegexSelectorCombinator = ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)';
|
|
12
|
+
export declare type RegexSelectorWithoutCompination = {
|
|
13
|
+
nodeName?: string;
|
|
14
|
+
attrName?: string;
|
|
15
|
+
attrValue?: string;
|
|
16
|
+
};
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPureHTMLElement(el: Element): boolean;
|