@asamuzakjp/dom-selector 0.20.2 → 0.21.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/package.json +13 -8
- package/src/js/matcher.js +259 -135
- package/src/js/parser.js +1 -1
- package/types/js/matcher.d.ts +8 -7
package/package.json
CHANGED
|
@@ -23,27 +23,32 @@
|
|
|
23
23
|
"is-potential-custom-element-name": "^1.0.1"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@types/css-tree": "^2.3.
|
|
26
|
+
"@types/css-tree": "^2.3.3",
|
|
27
27
|
"benchmark": "^2.1.4",
|
|
28
28
|
"c8": "^8.0.1",
|
|
29
29
|
"chai": "^4.3.10",
|
|
30
|
-
"eslint": "^8.
|
|
30
|
+
"eslint": "^8.52.0",
|
|
31
31
|
"eslint-config-standard": "^17.1.0",
|
|
32
|
-
"eslint-plugin-import": "^2.
|
|
32
|
+
"eslint-plugin-import": "^2.29.0",
|
|
33
33
|
"eslint-plugin-jsdoc": "^46.8.2",
|
|
34
|
-
"eslint-plugin-regexp": "^1.
|
|
34
|
+
"eslint-plugin-regexp": "^2.1.1",
|
|
35
35
|
"eslint-plugin-unicorn": "^48.0.1",
|
|
36
36
|
"jsdom": "^22.1.0",
|
|
37
37
|
"mocha": "^10.2.0",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"npm-run-all": "^4.1.5",
|
|
39
|
+
"sinon": "^17.0.0",
|
|
40
|
+
"typescript": "^5.2.2",
|
|
41
|
+
"wpt-runner": "^5.0.0"
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
44
|
"bench": "node benchmark/bench.js",
|
|
43
45
|
"build": "npm run tsc && npm run lint && npm test",
|
|
44
46
|
"lint": "eslint --fix .",
|
|
45
47
|
"test": "c8 --reporter=text mocha --exit test/**/*.test.js",
|
|
46
|
-
"
|
|
48
|
+
"test-wpt": "npm run update-wpt && node test/wpt/wpt-runner.js",
|
|
49
|
+
"tsc": "npx tsc",
|
|
50
|
+
"update": "npm-run-all -s update-*",
|
|
51
|
+
"update-wpt": "git submodule update --init --recursive"
|
|
47
52
|
},
|
|
48
|
-
"version": "0.
|
|
53
|
+
"version": "0.21.0"
|
|
49
54
|
}
|
package/src/js/matcher.js
CHANGED
|
@@ -63,6 +63,7 @@ const PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
|
|
|
63
63
|
export class Matcher {
|
|
64
64
|
/* private fields */
|
|
65
65
|
#ast;
|
|
66
|
+
#cache;
|
|
66
67
|
#node;
|
|
67
68
|
#nodes;
|
|
68
69
|
#root;
|
|
@@ -80,10 +81,11 @@ export class Matcher {
|
|
|
80
81
|
*/
|
|
81
82
|
constructor(selector, node, opt = {}) {
|
|
82
83
|
const { sort, warn } = opt;
|
|
83
|
-
|
|
84
|
+
this.#selector = selector;
|
|
84
85
|
this.#node = node;
|
|
86
|
+
[this.#ast, this.#nodes] = this._prepare(selector);
|
|
85
87
|
this.#root = this._getRoot(node);
|
|
86
|
-
this.#
|
|
88
|
+
this.#cache = new WeakMap();
|
|
87
89
|
this.#sort = !!sort;
|
|
88
90
|
this.#warn = !!warn;
|
|
89
91
|
}
|
|
@@ -123,7 +125,7 @@ export class Matcher {
|
|
|
123
125
|
root = node;
|
|
124
126
|
break;
|
|
125
127
|
}
|
|
126
|
-
|
|
128
|
+
case ELEMENT_NODE: {
|
|
127
129
|
if (isSameOrDescendant(node)) {
|
|
128
130
|
document = node.ownerDocument;
|
|
129
131
|
root = node.ownerDocument;
|
|
@@ -139,6 +141,10 @@ export class Matcher {
|
|
|
139
141
|
document = parent.ownerDocument;
|
|
140
142
|
root = parent;
|
|
141
143
|
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default: {
|
|
147
|
+
throw new TypeError(`Unexpected node type: ${node.nodeName}`);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
return {
|
|
@@ -149,8 +155,8 @@ export class Matcher {
|
|
|
149
155
|
|
|
150
156
|
/**
|
|
151
157
|
* sort AST leaves
|
|
152
|
-
* @param {object} leaves - leaves
|
|
153
|
-
* @returns {Array} - sorted leaves
|
|
158
|
+
* @param {Array.<object>} leaves - AST leaves
|
|
159
|
+
* @returns {Array.<object>} - sorted leaves
|
|
154
160
|
*/
|
|
155
161
|
_sortLeaves(leaves) {
|
|
156
162
|
const arr = [...leaves];
|
|
@@ -185,11 +191,11 @@ export class Matcher {
|
|
|
185
191
|
/**
|
|
186
192
|
* prepare ast and nodes
|
|
187
193
|
* @param {string} selector - CSS selector
|
|
188
|
-
* @returns {Array} - list and matrix
|
|
194
|
+
* @returns {Array.<Array.<object|undefined>>} - list and matrix
|
|
189
195
|
*/
|
|
190
196
|
_prepare(selector = this.#selector) {
|
|
191
197
|
const ast = parseSelector(selector);
|
|
192
|
-
const branches = walkAST(ast)
|
|
198
|
+
const branches = walkAST(ast);
|
|
193
199
|
const tree = [];
|
|
194
200
|
const nodes = [];
|
|
195
201
|
let i = 0;
|
|
@@ -202,7 +208,7 @@ export class Matcher {
|
|
|
202
208
|
if (item.type === COMBINATOR) {
|
|
203
209
|
const [nextItem] = items;
|
|
204
210
|
if (nextItem.type === COMBINATOR) {
|
|
205
|
-
const msg = `invalid combinator
|
|
211
|
+
const msg = `invalid combinator ${item.name}${nextItem.name}`;
|
|
206
212
|
throw new DOMException(msg, SYNTAX_ERR);
|
|
207
213
|
}
|
|
208
214
|
branch.push({
|
|
@@ -238,6 +244,42 @@ export class Matcher {
|
|
|
238
244
|
];
|
|
239
245
|
}
|
|
240
246
|
|
|
247
|
+
/**
|
|
248
|
+
* throw DOMExeption on pseudo element selector
|
|
249
|
+
* @param {object} astName - AST name
|
|
250
|
+
* @throws {DOMException}
|
|
251
|
+
* @returns {void}
|
|
252
|
+
*/
|
|
253
|
+
_throwOnPseudoElementSelector(astName) {
|
|
254
|
+
let msg;
|
|
255
|
+
let type;
|
|
256
|
+
switch (astName) {
|
|
257
|
+
case 'after':
|
|
258
|
+
case 'backdrop':
|
|
259
|
+
case 'before':
|
|
260
|
+
case 'cue':
|
|
261
|
+
case 'cue-region':
|
|
262
|
+
case 'first-letter':
|
|
263
|
+
case 'first-line':
|
|
264
|
+
case 'file-selector-button':
|
|
265
|
+
case 'marker':
|
|
266
|
+
case 'part':
|
|
267
|
+
case 'placeholder':
|
|
268
|
+
case 'selection':
|
|
269
|
+
case 'slotted':
|
|
270
|
+
case 'target-text': {
|
|
271
|
+
msg = `Unsupported pseudo-element ::${astName}`;
|
|
272
|
+
type = NOT_SUPPORTED_ERR;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
default: {
|
|
276
|
+
msg = `Unknown pseudo-element ::${astName}`;
|
|
277
|
+
type = SYNTAX_ERR;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
throw new DOMException(msg, type);
|
|
281
|
+
}
|
|
282
|
+
|
|
241
283
|
/**
|
|
242
284
|
* collect nth child
|
|
243
285
|
* @param {object} anb - An+B options
|
|
@@ -253,7 +295,13 @@ export class Matcher {
|
|
|
253
295
|
const { parentNode } = node;
|
|
254
296
|
const selectorNodes = new Set();
|
|
255
297
|
if (selector) {
|
|
256
|
-
|
|
298
|
+
let branches;
|
|
299
|
+
if (this.#cache.has(selector)) {
|
|
300
|
+
branches = this.#cache.get(selector);
|
|
301
|
+
} else {
|
|
302
|
+
branches = walkAST(selector);
|
|
303
|
+
this.#cache.set(selector, branches);
|
|
304
|
+
}
|
|
257
305
|
const branchLen = branches.length;
|
|
258
306
|
const iterator = [...parentNode.children].values();
|
|
259
307
|
for (const refNode of iterator) {
|
|
@@ -287,7 +335,7 @@ export class Matcher {
|
|
|
287
335
|
break;
|
|
288
336
|
}
|
|
289
337
|
}
|
|
290
|
-
} else {
|
|
338
|
+
} else if (!selector) {
|
|
291
339
|
const current = arr[b - 1];
|
|
292
340
|
matched.add(current);
|
|
293
341
|
}
|
|
@@ -318,7 +366,9 @@ export class Matcher {
|
|
|
318
366
|
}
|
|
319
367
|
}
|
|
320
368
|
} else if (i === nth) {
|
|
321
|
-
|
|
369
|
+
if (!selector) {
|
|
370
|
+
matched.add(current);
|
|
371
|
+
}
|
|
322
372
|
nth += a;
|
|
323
373
|
}
|
|
324
374
|
}
|
|
@@ -536,7 +586,7 @@ export class Matcher {
|
|
|
536
586
|
if (!node.hasAttribute('lang')) {
|
|
537
587
|
res = node;
|
|
538
588
|
}
|
|
539
|
-
} else if (/[A-
|
|
589
|
+
} else if (/[A-Z\d-]+/i.test(astName)) {
|
|
540
590
|
const codePart = '(?:-[A-Za-z\\d]+)?';
|
|
541
591
|
let reg;
|
|
542
592
|
if (/-/.test(astName)) {
|
|
@@ -574,7 +624,7 @@ export class Matcher {
|
|
|
574
624
|
|
|
575
625
|
/**
|
|
576
626
|
* match :has() pseudo-class function
|
|
577
|
-
* @param {Array} leaves - leaves
|
|
627
|
+
* @param {Array.<object>} leaves - AST leaves
|
|
578
628
|
* @param {object} node - Element node
|
|
579
629
|
* @returns {boolean} - result
|
|
580
630
|
*/
|
|
@@ -627,32 +677,22 @@ export class Matcher {
|
|
|
627
677
|
|
|
628
678
|
/**
|
|
629
679
|
* match logical pseudo-class functions - :has(), :is(), :not(), :where()
|
|
630
|
-
* @param {object}
|
|
680
|
+
* @param {object} astOpt - AST options
|
|
631
681
|
* @param {object} node - Element node
|
|
632
682
|
* @returns {?object} - matched node
|
|
633
683
|
*/
|
|
634
|
-
_matchLogicalPseudoFunc(
|
|
635
|
-
const branches =
|
|
636
|
-
const branchLen = branches.length;
|
|
637
|
-
const branchSelectors = [];
|
|
638
|
-
for (let i = 0; i < branchLen; i++) {
|
|
639
|
-
const leaves = branches[i].values();
|
|
640
|
-
for (const leaf of leaves) {
|
|
641
|
-
const css = generateCSS(leaf);
|
|
642
|
-
branchSelectors.push(css);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
const branchSelector = branchSelectors.join(',');
|
|
646
|
-
const astName = unescapeSelector(ast.name);
|
|
684
|
+
_matchLogicalPseudoFunc(astOpt = {}, node) {
|
|
685
|
+
const { astName = '', branches = [], selector = '' } = astOpt;
|
|
647
686
|
let res;
|
|
687
|
+
const branchLen = branches.length;
|
|
648
688
|
if (astName === 'has') {
|
|
649
|
-
if (
|
|
689
|
+
if (selector.includes(':has(')) {
|
|
650
690
|
res = null;
|
|
651
691
|
} else {
|
|
652
692
|
let bool;
|
|
653
693
|
for (let i = 0; i < branchLen; i++) {
|
|
654
694
|
const leaves = branches[i];
|
|
655
|
-
bool = this._matchHasPseudoFunc(leaves, node);
|
|
695
|
+
bool = this._matchHasPseudoFunc(Object.assign([], leaves), node);
|
|
656
696
|
if (bool) {
|
|
657
697
|
break;
|
|
658
698
|
}
|
|
@@ -663,7 +703,7 @@ export class Matcher {
|
|
|
663
703
|
}
|
|
664
704
|
// NOTE: according to MDN, :not() can not contain :not()
|
|
665
705
|
// but spec says nothing about that?
|
|
666
|
-
} else if (astName === 'not' &&
|
|
706
|
+
} else if (astName === 'not' && selector.includes(':not(')) {
|
|
667
707
|
res = null;
|
|
668
708
|
} else {
|
|
669
709
|
let bool;
|
|
@@ -699,7 +739,29 @@ export class Matcher {
|
|
|
699
739
|
let matched = new Set();
|
|
700
740
|
// :has(), :is(), :not(), :where()
|
|
701
741
|
if (PSEUDO_FUNC.test(astName)) {
|
|
702
|
-
|
|
742
|
+
let opt;
|
|
743
|
+
if (this.#cache.has(ast)) {
|
|
744
|
+
opt = this.#cache.get(ast);
|
|
745
|
+
} else {
|
|
746
|
+
const branches = walkAST(ast);
|
|
747
|
+
const branchLen = branches.length;
|
|
748
|
+
const branchSelectors = [];
|
|
749
|
+
for (let i = 0; i < branchLen; i++) {
|
|
750
|
+
const leaves = branches[i];
|
|
751
|
+
for (const leaf of leaves) {
|
|
752
|
+
const css = generateCSS(leaf);
|
|
753
|
+
branchSelectors.push(css);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const selector = branchSelectors.join(',');
|
|
757
|
+
opt = {
|
|
758
|
+
astName,
|
|
759
|
+
branches,
|
|
760
|
+
selector
|
|
761
|
+
};
|
|
762
|
+
this.#cache.set(ast, opt);
|
|
763
|
+
}
|
|
764
|
+
const res = this._matchLogicalPseudoFunc(opt, node);
|
|
703
765
|
if (res) {
|
|
704
766
|
matched.add(res);
|
|
705
767
|
}
|
|
@@ -958,7 +1020,7 @@ export class Matcher {
|
|
|
958
1020
|
if (!parent) {
|
|
959
1021
|
parent = root;
|
|
960
1022
|
}
|
|
961
|
-
const nodes = [...parent.getElementsByTagName('input')]
|
|
1023
|
+
const nodes = [...parent.getElementsByTagName('input')];
|
|
962
1024
|
let checked;
|
|
963
1025
|
for (const item of nodes) {
|
|
964
1026
|
if (item.getAttribute('type') === 'radio') {
|
|
@@ -1164,8 +1226,9 @@ export class Matcher {
|
|
|
1164
1226
|
if (node.hasChildNodes()) {
|
|
1165
1227
|
const nodes = node.childNodes.values();
|
|
1166
1228
|
let bool;
|
|
1167
|
-
for (const
|
|
1168
|
-
bool =
|
|
1229
|
+
for (const refNode of nodes) {
|
|
1230
|
+
bool = refNode.nodeType !== ELEMENT_NODE &&
|
|
1231
|
+
refNode.nodeType !== TEXT_NODE;
|
|
1169
1232
|
if (!bool) {
|
|
1170
1233
|
break;
|
|
1171
1234
|
}
|
|
@@ -1511,44 +1574,6 @@ export class Matcher {
|
|
|
1511
1574
|
return res ?? null;
|
|
1512
1575
|
}
|
|
1513
1576
|
|
|
1514
|
-
/**
|
|
1515
|
-
* match pseudo-element selector
|
|
1516
|
-
* @param {object} ast - AST
|
|
1517
|
-
* @param {object} node - Element node
|
|
1518
|
-
* @throws {DOMException}
|
|
1519
|
-
* @returns {void}
|
|
1520
|
-
*/
|
|
1521
|
-
_matchPseudoElementSelector(ast, node) {
|
|
1522
|
-
const astName = unescapeSelector(ast.name);
|
|
1523
|
-
let msg;
|
|
1524
|
-
let type;
|
|
1525
|
-
switch (astName) {
|
|
1526
|
-
case 'after':
|
|
1527
|
-
case 'backdrop':
|
|
1528
|
-
case 'before':
|
|
1529
|
-
case 'cue':
|
|
1530
|
-
case 'cue-region':
|
|
1531
|
-
case 'first-letter':
|
|
1532
|
-
case 'first-line':
|
|
1533
|
-
case 'file-selector-button':
|
|
1534
|
-
case 'marker':
|
|
1535
|
-
case 'part':
|
|
1536
|
-
case 'placeholder':
|
|
1537
|
-
case 'selection':
|
|
1538
|
-
case 'slotted':
|
|
1539
|
-
case 'target-text': {
|
|
1540
|
-
msg = `Unsupported pseudo-element ::${astName}`;
|
|
1541
|
-
type = NOT_SUPPORTED_ERR;
|
|
1542
|
-
break;
|
|
1543
|
-
}
|
|
1544
|
-
default: {
|
|
1545
|
-
msg = `Unknown pseudo-element ::${astName}`;
|
|
1546
|
-
type = SYNTAX_ERR;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
throw new DOMException(msg, type);
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
1577
|
/**
|
|
1553
1578
|
* match type selector
|
|
1554
1579
|
* @param {object} ast - AST
|
|
@@ -1633,7 +1658,8 @@ export class Matcher {
|
|
|
1633
1658
|
break;
|
|
1634
1659
|
}
|
|
1635
1660
|
case PSEUDO_ELEMENT_SELECTOR: {
|
|
1636
|
-
|
|
1661
|
+
const astName = unescapeSelector(ast.name);
|
|
1662
|
+
this._throwOnPseudoElementSelector(astName);
|
|
1637
1663
|
break;
|
|
1638
1664
|
}
|
|
1639
1665
|
case TYPE_SELECTOR:
|
|
@@ -1650,14 +1676,13 @@ export class Matcher {
|
|
|
1650
1676
|
|
|
1651
1677
|
/**
|
|
1652
1678
|
* match leaves
|
|
1653
|
-
* @param {Array} leaves - leaves
|
|
1679
|
+
* @param {Array.<object>} leaves - AST leaves
|
|
1654
1680
|
* @param {object} node - node
|
|
1655
1681
|
* @returns {boolean} - result
|
|
1656
1682
|
*/
|
|
1657
1683
|
_matchLeaves(leaves, node) {
|
|
1658
|
-
const iterator = leaves.values();
|
|
1659
1684
|
let bool;
|
|
1660
|
-
for (const leaf of
|
|
1685
|
+
for (const leaf of leaves) {
|
|
1661
1686
|
bool = this._matchSelector(leaf, node).has(node);
|
|
1662
1687
|
if (!bool) {
|
|
1663
1688
|
break;
|
|
@@ -1666,6 +1691,96 @@ export class Matcher {
|
|
|
1666
1691
|
return !!bool;
|
|
1667
1692
|
}
|
|
1668
1693
|
|
|
1694
|
+
/**
|
|
1695
|
+
* find descendant nodes
|
|
1696
|
+
* @param {Array.<object>} leaves - AST leaves
|
|
1697
|
+
* @param {object} baseNode - base Element node
|
|
1698
|
+
* @returns {object} - result
|
|
1699
|
+
*/
|
|
1700
|
+
_findDescendantNodes(leaves, baseNode) {
|
|
1701
|
+
const [leaf, ...items] = leaves;
|
|
1702
|
+
const { type: leafType } = leaf;
|
|
1703
|
+
const leafName = unescapeSelector(leaf.name);
|
|
1704
|
+
const matchItems = items.length > 0;
|
|
1705
|
+
const { document, root } = this.#root;
|
|
1706
|
+
let nodes = new Set();
|
|
1707
|
+
let pending = false;
|
|
1708
|
+
switch (leafType) {
|
|
1709
|
+
case ID_SELECTOR: {
|
|
1710
|
+
if (root.nodeType === ELEMENT_NODE) {
|
|
1711
|
+
pending = true;
|
|
1712
|
+
} else {
|
|
1713
|
+
const elm = root.getElementById(leafName);
|
|
1714
|
+
if (elm && elm !== baseNode) {
|
|
1715
|
+
const bool = isSameOrDescendant(elm, baseNode);
|
|
1716
|
+
let node;
|
|
1717
|
+
if (bool) {
|
|
1718
|
+
node = elm;
|
|
1719
|
+
}
|
|
1720
|
+
if (node) {
|
|
1721
|
+
if (matchItems) {
|
|
1722
|
+
const bool = this._matchLeaves(items, node);
|
|
1723
|
+
if (bool) {
|
|
1724
|
+
nodes.add(node);
|
|
1725
|
+
}
|
|
1726
|
+
} else {
|
|
1727
|
+
nodes.add(node);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
break;
|
|
1733
|
+
}
|
|
1734
|
+
case CLASS_SELECTOR: {
|
|
1735
|
+
const arr = [...baseNode.getElementsByClassName(leafName)];
|
|
1736
|
+
if (arr.length) {
|
|
1737
|
+
if (matchItems) {
|
|
1738
|
+
for (const node of arr) {
|
|
1739
|
+
const bool = this._matchLeaves(items, node);
|
|
1740
|
+
if (bool) {
|
|
1741
|
+
nodes.add(node);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
} else {
|
|
1745
|
+
nodes = new Set(arr);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
break;
|
|
1749
|
+
}
|
|
1750
|
+
case TYPE_SELECTOR: {
|
|
1751
|
+
if (document.contentType !== 'text/html' || /[*|]/.test(leafName)) {
|
|
1752
|
+
pending = true;
|
|
1753
|
+
} else {
|
|
1754
|
+
const arr = [...baseNode.getElementsByTagName(leafName)];
|
|
1755
|
+
if (arr.length) {
|
|
1756
|
+
if (matchItems) {
|
|
1757
|
+
for (const node of arr) {
|
|
1758
|
+
const bool = this._matchLeaves(items, node);
|
|
1759
|
+
if (bool) {
|
|
1760
|
+
nodes.add(node);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
} else {
|
|
1764
|
+
nodes = new Set(arr);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
break;
|
|
1769
|
+
}
|
|
1770
|
+
case PSEUDO_ELEMENT_SELECTOR: {
|
|
1771
|
+
this._throwOnPseudoElementSelector(leafName);
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
default: {
|
|
1775
|
+
pending = true;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return {
|
|
1779
|
+
nodes,
|
|
1780
|
+
pending
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1669
1784
|
/**
|
|
1670
1785
|
* match combinator
|
|
1671
1786
|
* @param {object} twig - twig
|
|
@@ -1703,8 +1818,8 @@ export class Matcher {
|
|
|
1703
1818
|
break;
|
|
1704
1819
|
}
|
|
1705
1820
|
case '>': {
|
|
1706
|
-
const
|
|
1707
|
-
for (const refNode of
|
|
1821
|
+
const childNodes = [...node.children];
|
|
1822
|
+
for (const refNode of childNodes) {
|
|
1708
1823
|
const bool = this._matchLeaves(leaves, refNode);
|
|
1709
1824
|
if (bool) {
|
|
1710
1825
|
matched.add(refNode);
|
|
@@ -1714,18 +1829,23 @@ export class Matcher {
|
|
|
1714
1829
|
}
|
|
1715
1830
|
case ' ':
|
|
1716
1831
|
default: {
|
|
1717
|
-
const {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
if (
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1832
|
+
const { nodes, pending } = this._findDescendantNodes(leaves, node);
|
|
1833
|
+
if (nodes.size) {
|
|
1834
|
+
matched = nodes;
|
|
1835
|
+
} else if (pending) {
|
|
1836
|
+
const { document } = this.#root;
|
|
1837
|
+
const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
|
|
1838
|
+
let refNode = iterator.nextNode();
|
|
1839
|
+
if (refNode === node) {
|
|
1840
|
+
refNode = iterator.nextNode();
|
|
1841
|
+
}
|
|
1842
|
+
while (refNode) {
|
|
1843
|
+
const bool = this._matchLeaves(leaves, refNode);
|
|
1844
|
+
if (bool) {
|
|
1845
|
+
matched.add(refNode);
|
|
1846
|
+
}
|
|
1847
|
+
refNode = iterator.nextNode();
|
|
1727
1848
|
}
|
|
1728
|
-
refNode = iterator.nextNode();
|
|
1729
1849
|
}
|
|
1730
1850
|
}
|
|
1731
1851
|
}
|
|
@@ -1855,8 +1975,8 @@ export class Matcher {
|
|
|
1855
1975
|
}
|
|
1856
1976
|
}
|
|
1857
1977
|
} else if (root.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1858
|
-
const
|
|
1859
|
-
for (const node of
|
|
1978
|
+
const childNodes = [...root.children];
|
|
1979
|
+
for (const node of childNodes) {
|
|
1860
1980
|
if (node.classList.contains(leafName)) {
|
|
1861
1981
|
arr.push(node);
|
|
1862
1982
|
}
|
|
@@ -1873,8 +1993,7 @@ export class Matcher {
|
|
|
1873
1993
|
}
|
|
1874
1994
|
if (arr.length) {
|
|
1875
1995
|
if (matchItems) {
|
|
1876
|
-
const
|
|
1877
|
-
for (const node of iterator) {
|
|
1996
|
+
for (const node of arr) {
|
|
1878
1997
|
const bool = this._matchLeaves(items, node);
|
|
1879
1998
|
if (bool) {
|
|
1880
1999
|
nodes.add(node);
|
|
@@ -1916,8 +2035,8 @@ export class Matcher {
|
|
|
1916
2035
|
const a = [...document.getElementsByTagName(leafName)];
|
|
1917
2036
|
arr.push(...a);
|
|
1918
2037
|
} else if (root.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1919
|
-
const
|
|
1920
|
-
for (const node of
|
|
2038
|
+
const childNodes = [...root.children];
|
|
2039
|
+
for (const node of childNodes) {
|
|
1921
2040
|
if (node.localName === tagName) {
|
|
1922
2041
|
arr.push(node);
|
|
1923
2042
|
}
|
|
@@ -1934,8 +2053,7 @@ export class Matcher {
|
|
|
1934
2053
|
}
|
|
1935
2054
|
if (arr.length) {
|
|
1936
2055
|
if (matchItems) {
|
|
1937
|
-
const
|
|
1938
|
-
for (const node of iterator) {
|
|
2056
|
+
for (const node of arr) {
|
|
1939
2057
|
const bool = this._matchLeaves(items, node);
|
|
1940
2058
|
if (bool) {
|
|
1941
2059
|
nodes.add(node);
|
|
@@ -1948,6 +2066,7 @@ export class Matcher {
|
|
|
1948
2066
|
break;
|
|
1949
2067
|
}
|
|
1950
2068
|
case PSEUDO_ELEMENT_SELECTOR: {
|
|
2069
|
+
this._throwOnPseudoElementSelector(leafName);
|
|
1951
2070
|
break;
|
|
1952
2071
|
}
|
|
1953
2072
|
default: {
|
|
@@ -1960,7 +2079,7 @@ export class Matcher {
|
|
|
1960
2079
|
} else if (targetType === TARGET_LINEAL) {
|
|
1961
2080
|
let refNode = this.#node;
|
|
1962
2081
|
while (refNode) {
|
|
1963
|
-
const bool = this._matchLeaves([leaf], refNode);
|
|
2082
|
+
const bool = this._matchLeaves([leaf], refNode);
|
|
1964
2083
|
if (bool) {
|
|
1965
2084
|
arr.push(refNode);
|
|
1966
2085
|
}
|
|
@@ -1971,8 +2090,7 @@ export class Matcher {
|
|
|
1971
2090
|
}
|
|
1972
2091
|
if (arr.length) {
|
|
1973
2092
|
if (matchItems) {
|
|
1974
|
-
const
|
|
1975
|
-
for (const node of iterator) {
|
|
2093
|
+
for (const node of arr) {
|
|
1976
2094
|
const bool = this._matchLeaves(items, node);
|
|
1977
2095
|
if (bool) {
|
|
1978
2096
|
nodes.add(node);
|
|
@@ -1993,63 +2111,63 @@ export class Matcher {
|
|
|
1993
2111
|
/**
|
|
1994
2112
|
* collect nodes
|
|
1995
2113
|
* @param {string} targetType - target type
|
|
1996
|
-
* @returns {Array} - matrix
|
|
2114
|
+
* @returns {Array.<Array.<object|undefined>>} - matrix
|
|
1997
2115
|
*/
|
|
1998
2116
|
_collectNodes(targetType) {
|
|
1999
|
-
const pendingItems = new Set();
|
|
2000
2117
|
const ast = this.#ast.values();
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2118
|
+
if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
|
|
2119
|
+
const pendingItems = new Set();
|
|
2120
|
+
let i = 0;
|
|
2121
|
+
for (const { branch } of ast) {
|
|
2004
2122
|
const twig = branch[0];
|
|
2005
2123
|
const { nodes, pending } = this._findNodes(twig, targetType);
|
|
2006
2124
|
if (nodes.size) {
|
|
2007
2125
|
this.#nodes[i] = nodes;
|
|
2008
2126
|
} else if (pending) {
|
|
2009
2127
|
pendingItems.add(new Map([
|
|
2010
|
-
['
|
|
2128
|
+
['index', i],
|
|
2011
2129
|
['twig', twig]
|
|
2012
2130
|
]));
|
|
2013
2131
|
} else {
|
|
2014
2132
|
this.#ast[i].skip = true;
|
|
2015
2133
|
}
|
|
2016
|
-
|
|
2017
|
-
const branchLen = branch.length;
|
|
2018
|
-
const lastIndex = branchLen - 1;
|
|
2019
|
-
const twig = branch[lastIndex];
|
|
2020
|
-
const { nodes } = this._findNodes(twig, targetType);
|
|
2021
|
-
if (nodes.size) {
|
|
2022
|
-
this.#nodes[i] = nodes;
|
|
2023
|
-
} else {
|
|
2024
|
-
this.#ast[i].skip = true;
|
|
2025
|
-
}
|
|
2134
|
+
i++;
|
|
2026
2135
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
while (nextNode) {
|
|
2034
|
-
let bool = false;
|
|
2035
|
-
if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
|
|
2136
|
+
if (pendingItems.size) {
|
|
2137
|
+
const { document, root } = this.#root;
|
|
2138
|
+
const iterator = document.createNodeIterator(root, SHOW_ELEMENT);
|
|
2139
|
+
let nextNode = iterator.nextNode();
|
|
2140
|
+
while (nextNode) {
|
|
2141
|
+
let bool = false;
|
|
2036
2142
|
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
2037
2143
|
bool = isSameOrDescendant(nextNode, this.#node);
|
|
2038
2144
|
} else {
|
|
2039
2145
|
bool = true;
|
|
2040
2146
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2147
|
+
if (bool) {
|
|
2148
|
+
for (const pendingItem of pendingItems) {
|
|
2149
|
+
const { leaves } = pendingItem.get('twig');
|
|
2150
|
+
const matched = this._matchLeaves(leaves, nextNode);
|
|
2151
|
+
if (matched) {
|
|
2152
|
+
const index = pendingItem.get('index');
|
|
2153
|
+
this.#nodes[index].add(nextNode);
|
|
2154
|
+
}
|
|
2049
2155
|
}
|
|
2050
2156
|
}
|
|
2157
|
+
nextNode = iterator.nextNode();
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
} else {
|
|
2161
|
+
let i = 0;
|
|
2162
|
+
for (const { branch } of ast) {
|
|
2163
|
+
const twig = branch[branch.length - 1];
|
|
2164
|
+
const { nodes } = this._findNodes(twig, targetType);
|
|
2165
|
+
if (nodes.size) {
|
|
2166
|
+
this.#nodes[i] = nodes;
|
|
2167
|
+
} else {
|
|
2168
|
+
this.#ast[i].skip = true;
|
|
2051
2169
|
}
|
|
2052
|
-
|
|
2170
|
+
i++;
|
|
2053
2171
|
}
|
|
2054
2172
|
}
|
|
2055
2173
|
return [
|
|
@@ -2061,7 +2179,7 @@ export class Matcher {
|
|
|
2061
2179
|
/**
|
|
2062
2180
|
* sort nodes
|
|
2063
2181
|
* @param {object} nodes - collection of nodes
|
|
2064
|
-
* @returns {Array} - collection of sorted nodes
|
|
2182
|
+
* @returns {Array.<object|undefined>} - collection of sorted nodes
|
|
2065
2183
|
*/
|
|
2066
2184
|
_sortNodes(nodes) {
|
|
2067
2185
|
const arr = [...nodes];
|
|
@@ -2214,6 +2332,9 @@ export class Matcher {
|
|
|
2214
2332
|
* @returns {boolean} - `true` if matched `false` otherwise
|
|
2215
2333
|
*/
|
|
2216
2334
|
matches() {
|
|
2335
|
+
if (this.#node.nodeType !== ELEMENT_NODE) {
|
|
2336
|
+
throw new TypeError(`Unexpected node type: ${this.#node.nodeName}`);
|
|
2337
|
+
}
|
|
2217
2338
|
let res;
|
|
2218
2339
|
try {
|
|
2219
2340
|
const nodes = this._find(TARGET_SELF);
|
|
@@ -2229,6 +2350,9 @@ export class Matcher {
|
|
|
2229
2350
|
* @returns {?object} - matched node
|
|
2230
2351
|
*/
|
|
2231
2352
|
closest() {
|
|
2353
|
+
if (this.#node.nodeType !== ELEMENT_NODE) {
|
|
2354
|
+
throw new TypeError(`Unexpected node type: ${this.#node.nodeName}`);
|
|
2355
|
+
}
|
|
2232
2356
|
let res;
|
|
2233
2357
|
try {
|
|
2234
2358
|
const nodes = this._find(TARGET_LINEAL);
|
package/src/js/parser.js
CHANGED
|
@@ -72,7 +72,7 @@ export const unescapeSelector = (selector = '') => {
|
|
|
72
72
|
*/
|
|
73
73
|
export const preprocess = (...args) => {
|
|
74
74
|
if (!args.length) {
|
|
75
|
-
throw new TypeError('1 argument required, but only 0 present');
|
|
75
|
+
throw new TypeError('1 argument required, but only 0 present.');
|
|
76
76
|
}
|
|
77
77
|
let [selector] = args;
|
|
78
78
|
if (typeof selector === 'string') {
|
package/types/js/matcher.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export class Matcher {
|
|
|
5
5
|
});
|
|
6
6
|
_onError(e: Error): void;
|
|
7
7
|
_getRoot(node?: object): object;
|
|
8
|
-
_sortLeaves(leaves: object):
|
|
9
|
-
_prepare(selector?: string):
|
|
8
|
+
_sortLeaves(leaves: Array<object>): Array<object>;
|
|
9
|
+
_prepare(selector?: string): Array<Array<object | undefined>>;
|
|
10
10
|
_collectNthChild(anb: {
|
|
11
11
|
a: number;
|
|
12
12
|
b: number;
|
|
@@ -21,8 +21,8 @@ export class Matcher {
|
|
|
21
21
|
_matchAnPlusB(ast: object, node: object, nthName: string): object;
|
|
22
22
|
_matchDirectionPseudoClass(ast: object, node: object): object | null;
|
|
23
23
|
_matchLanguagePseudoClass(ast: object, node: object): object | null;
|
|
24
|
-
_matchHasPseudoFunc(leaves:
|
|
25
|
-
_matchLogicalPseudoFunc(
|
|
24
|
+
_matchHasPseudoFunc(leaves: Array<object>, node: object): boolean;
|
|
25
|
+
_matchLogicalPseudoFunc(astOpt: object, node: object): object | null;
|
|
26
26
|
_matchPseudoClassSelector(ast: object, node: object): object;
|
|
27
27
|
_matchAttributeSelector(ast: object, node: object): object | null;
|
|
28
28
|
_matchClassSelector(ast: object, node: object): object | null;
|
|
@@ -30,13 +30,14 @@ export class Matcher {
|
|
|
30
30
|
_matchPseudoElementSelector(ast: object, node: object): void;
|
|
31
31
|
_matchTypeSelector(ast: object, node: object): object | null;
|
|
32
32
|
_matchSelector(ast: object, node: object): object;
|
|
33
|
-
_matchLeaves(leaves:
|
|
33
|
+
_matchLeaves(leaves: Array<object>, node: object): boolean;
|
|
34
|
+
_findDescendantNodes(leaves: Array<object>, refNode: object): object;
|
|
34
35
|
_matchCombinator(twig: object, node: object, opt?: {
|
|
35
36
|
find?: string;
|
|
36
37
|
}): object;
|
|
37
38
|
_findNodes(twig: object, targetType: string): object;
|
|
38
|
-
_collectNodes(targetType: string):
|
|
39
|
-
_sortNodes(nodes: object):
|
|
39
|
+
_collectNodes(targetType: string): Array<Array<object | undefined>>;
|
|
40
|
+
_sortNodes(nodes: object): Array<object | undefined>;
|
|
40
41
|
_matchNodes(targetType: string): object;
|
|
41
42
|
_find(targetType: string): object;
|
|
42
43
|
matches(): boolean;
|