@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 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.2",
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.51.0",
30
+ "eslint": "^8.52.0",
31
31
  "eslint-config-standard": "^17.1.0",
32
- "eslint-plugin-import": "^2.28.1",
32
+ "eslint-plugin-import": "^2.29.0",
33
33
  "eslint-plugin-jsdoc": "^46.8.2",
34
- "eslint-plugin-regexp": "^1.15.0",
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
- "sinon": "^16.1.0",
39
- "typescript": "^5.2.2"
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
- "tsc": "npx tsc"
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.20.2"
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
- [this.#ast, this.#nodes] = this._prepare(selector);
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.#selector = selector;
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
- default: {
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).values();
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, ${item.name}${nextItem.name}`;
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
- const branches = walkAST(selector);
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
- matched.add(current);
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-Za-z\d-]+/.test(astName)) {
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} ast - AST
680
+ * @param {object} astOpt - AST options
631
681
  * @param {object} node - Element node
632
682
  * @returns {?object} - matched node
633
683
  */
634
- _matchLogicalPseudoFunc(ast, node) {
635
- const branches = walkAST(ast);
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 (branchSelector.includes(':has(')) {
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' && branchSelector.includes(':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
- const res = this._matchLogicalPseudoFunc(ast, node);
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')].values();
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 n of nodes) {
1168
- bool = n.nodeType !== ELEMENT_NODE && n.nodeType !== TEXT_NODE;
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
- this._matchPseudoElementSelector(ast, node);
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 iterator) {
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 iterator = [...node.children].values();
1707
- for (const refNode of iterator) {
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 { document } = this.#root;
1718
- const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
1719
- let refNode = iterator.nextNode();
1720
- if (refNode === node) {
1721
- refNode = iterator.nextNode();
1722
- }
1723
- while (refNode) {
1724
- const bool = this._matchLeaves(leaves, refNode);
1725
- if (bool) {
1726
- matched.add(refNode);
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 iterator = [...root.children].values();
1859
- for (const node of iterator) {
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 iterator = arr.values();
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 iterator = [...root.children].values();
1920
- for (const node of iterator) {
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 iterator = arr.values();
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 iterator = arr.values();
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
- let i = 0;
2002
- for (const { branch } of ast) {
2003
- if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
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
- ['i', i],
2128
+ ['index', i],
2011
2129
  ['twig', twig]
2012
2130
  ]));
2013
2131
  } else {
2014
2132
  this.#ast[i].skip = true;
2015
2133
  }
2016
- } else {
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
- i++;
2028
- }
2029
- if (pendingItems.size) {
2030
- const { document, root } = this.#root;
2031
- const iterator = document.createNodeIterator(root, SHOW_ELEMENT);
2032
- let nextNode = iterator.nextNode();
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
- if (bool) {
2043
- for (const pendingItem of pendingItems) {
2044
- const { leaves } = pendingItem.get('twig');
2045
- const matched = this._matchLeaves(leaves, nextNode);
2046
- if (matched) {
2047
- const indexI = pendingItem.get('i');
2048
- this.#nodes[indexI].add(nextNode);
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
- nextNode = iterator.nextNode();
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') {
@@ -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): any[];
9
- _prepare(selector?: string): any[];
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: any[], node: object): boolean;
25
- _matchLogicalPseudoFunc(ast: object, node: object): object | null;
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: any[], node: object): boolean;
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): any[];
39
- _sortNodes(nodes: object): any[];
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;