@asamuzakjp/dom-selector 7.0.1 → 7.0.3

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,13 +23,12 @@
23
23
  },
24
24
  "./package.json": "./package.json"
25
25
  },
26
- "types": "types/index.d.ts",
27
26
  "dependencies": {
28
27
  "@asamuzakjp/nwsapi": "^2.3.9",
29
28
  "bidi-js": "^1.0.3",
30
- "css-tree": "^3.1.0",
29
+ "css-tree": "^3.2.1",
31
30
  "is-potential-custom-element-name": "^1.0.1",
32
- "lru-cache": "^11.2.6"
31
+ "lru-cache": "^11.2.7"
33
32
  },
34
33
  "devDependencies": {
35
34
  "@types/css-tree": "^2.3.11",
@@ -37,18 +36,18 @@
37
36
  "c8": "^11.0.0",
38
37
  "chai": "^6.2.2",
39
38
  "commander": "^14.0.3",
40
- "eslint": "^9.39.3",
39
+ "eslint": "^9.39.4",
41
40
  "eslint-config-prettier": "^10.1.8",
42
- "eslint-plugin-jsdoc": "^62.7.1",
41
+ "eslint-plugin-jsdoc": "^62.8.0",
43
42
  "eslint-plugin-prettier": "^5.5.5",
44
- "eslint-plugin-regexp": "^3.0.0",
43
+ "eslint-plugin-regexp": "^3.1.0",
45
44
  "eslint-plugin-unicorn": "^63.0.0",
46
45
  "globals": "^17.4.0",
47
46
  "jsdom": "^28.1.0",
48
47
  "mocha": "^11.7.5",
49
48
  "neostandard": "^0.13.0",
50
49
  "prettier": "^3.8.1",
51
- "sinon": "^21.0.1",
50
+ "sinon": "^21.0.2",
52
51
  "typescript": "^5.9.3",
53
52
  "wpt-runner": "^6.1.0"
54
53
  },
@@ -72,5 +71,5 @@
72
71
  "engines": {
73
72
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
74
73
  },
75
- "version": "7.0.1"
74
+ "version": "7.0.3"
76
75
  }
package/src/index.js CHANGED
@@ -125,9 +125,7 @@ export class DOMSelector {
125
125
  opt.check = true;
126
126
  opt.noexcept = true;
127
127
  opt.warn = false;
128
- this.#finder.setup(selector, node, opt);
129
- const res = this.#finder.find(TARGET_SELF);
130
- return res;
128
+ return this.#finder.setup(selector, node, opt).find(TARGET_SELF);
131
129
  };
132
130
 
133
131
  /**
@@ -163,8 +161,7 @@ export class DOMSelector {
163
161
  if (filterMatches) {
164
162
  try {
165
163
  const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
166
- const res = this.#nwsapi.match(selector, n);
167
- return res;
164
+ return this.#nwsapi.match(selector, n);
168
165
  } catch (e) {
169
166
  // fall through
170
167
  }
@@ -175,8 +172,7 @@ export class DOMSelector {
175
172
  if (this.#idlUtils) {
176
173
  node = this.#idlUtils.wrapperForImpl(node);
177
174
  }
178
- this.#finder.setup(selector, node, opt);
179
- const nodes = this.#finder.find(TARGET_SELF);
175
+ const nodes = this.#finder.setup(selector, node, opt).find(TARGET_SELF);
180
176
  res = nodes.size;
181
177
  } catch (e) {
182
178
  this.#finder.onError(e, opt);
@@ -217,8 +213,7 @@ export class DOMSelector {
217
213
  if (filterMatches) {
218
214
  try {
219
215
  const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
220
- const res = this.#nwsapi.closest(selector, n);
221
- return res;
216
+ return this.#nwsapi.closest(selector, n);
222
217
  } catch (e) {
223
218
  // fall through
224
219
  }
@@ -229,8 +224,7 @@ export class DOMSelector {
229
224
  if (this.#idlUtils) {
230
225
  node = this.#idlUtils.wrapperForImpl(node);
231
226
  }
232
- this.#finder.setup(selector, node, opt);
233
- const nodes = this.#finder.find(TARGET_LINEAL);
227
+ const nodes = this.#finder.setup(selector, node, opt).find(TARGET_LINEAL);
234
228
  if (nodes.size) {
235
229
  let refNode = node;
236
230
  while (refNode) {
@@ -259,13 +253,37 @@ export class DOMSelector {
259
253
  const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
260
254
  return this.#finder.onError(e, opt);
261
255
  }
256
+ const document =
257
+ node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;
258
+ if (
259
+ document === this.#document &&
260
+ document.contentType === 'text/html' &&
261
+ document.documentElement &&
262
+ (node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host)
263
+ ) {
264
+ const cacheKey = `querySelector_${selector}`;
265
+ let filterMatches = false;
266
+ if (this.#cache.has(cacheKey)) {
267
+ filterMatches = this.#cache.get(cacheKey);
268
+ } else {
269
+ filterMatches = filterSelector(selector, TARGET_FIRST);
270
+ this.#cache.set(cacheKey, filterMatches);
271
+ }
272
+ if (filterMatches) {
273
+ try {
274
+ const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
275
+ return this.#nwsapi.first(selector, n);
276
+ } catch (e) {
277
+ // fall through
278
+ }
279
+ }
280
+ }
262
281
  let res;
263
282
  try {
264
283
  if (this.#idlUtils) {
265
284
  node = this.#idlUtils.wrapperForImpl(node);
266
285
  }
267
- this.#finder.setup(selector, node, opt);
268
- const nodes = this.#finder.find(TARGET_FIRST);
286
+ const nodes = this.#finder.setup(selector, node, opt).find(TARGET_FIRST);
269
287
  if (nodes.size) {
270
288
  [res] = [...nodes];
271
289
  }
@@ -307,8 +325,7 @@ export class DOMSelector {
307
325
  if (filterMatches) {
308
326
  try {
309
327
  const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
310
- const res = this.#nwsapi.select(selector, n);
311
- return res;
328
+ return this.#nwsapi.select(selector, n);
312
329
  } catch (e) {
313
330
  // fall through
314
331
  }
@@ -319,8 +336,7 @@ export class DOMSelector {
319
336
  if (this.#idlUtils) {
320
337
  node = this.#idlUtils.wrapperForImpl(node);
321
338
  }
322
- this.#finder.setup(selector, node, opt);
323
- const nodes = this.#finder.find(TARGET_ALL);
339
+ const nodes = this.#finder.setup(selector, node, opt).find(TARGET_ALL);
324
340
  if (nodes.size) {
325
341
  res = [...nodes];
326
342
  }
package/src/js/finder.js CHANGED
@@ -275,17 +275,18 @@ export class Finder {
275
275
  * @returns {Array.<void>} An array of return values from addEventListener.
276
276
  */
277
277
  _registerEventListeners = () => {
278
- const opt = {
279
- capture: true,
280
- passive: true
281
- };
282
278
  const func = [];
283
279
  for (const eventHandler of this.#eventHandlers) {
284
280
  const { keys, handler } = eventHandler;
285
281
  const l = keys.length;
286
282
  for (let i = 0; i < l; i++) {
287
283
  const key = keys[i];
288
- func.push(this.#window.addEventListener(key, handler, opt));
284
+ func.push(
285
+ this.#window.addEventListener(key, handler, {
286
+ capture: true,
287
+ passive: true
288
+ })
289
+ );
289
290
  }
290
291
  }
291
292
  return func;
@@ -454,10 +455,10 @@ export class Finder {
454
455
  * @private
455
456
  * @param {object} parentNode - The parent element.
456
457
  * @param {Array.<Array.<object>>} selectorBranches - The selector branches.
457
- * @param {object} [opt] - Options.
458
+ * @param {object} opt - Options.
458
459
  * @returns {Array.<object>} An array of child nodes.
459
460
  */
460
- _getFilteredChildren = (parentNode, selectorBranches, opt = {}) => {
461
+ _getFilteredChildren = (parentNode, selectorBranches, opt) => {
461
462
  const children = [];
462
463
  const walker = this._createTreeWalker(parentNode, { force: true });
463
464
  let childNode = walker.firstChild();
@@ -496,10 +497,10 @@ export class Finder {
496
497
  * @param {boolean} [anb.reverse] - If true, reverses the order.
497
498
  * @param {object} [anb.selector] - The AST.
498
499
  * @param {object} node - The Element node.
499
- * @param {object} [opt] - Options.
500
+ * @param {object} opt - Options.
500
501
  * @returns {Set.<object>} A collection of matched nodes.
501
502
  */
502
- _collectNthChild = (anb, node, opt = {}) => {
503
+ _collectNthChild = (anb, node, opt) => {
503
504
  const { a, b, selector } = anb;
504
505
  const { parentNode } = node;
505
506
  if (!parentNode) {
@@ -574,10 +575,10 @@ export class Finder {
574
575
  * @param {object} ast - The AST.
575
576
  * @param {object} node - The Element node.
576
577
  * @param {string} nthName - The name of the nth pseudo-class.
577
- * @param {object} [opt] - Options.
578
+ * @param {object} opt - Options.
578
579
  * @returns {Set.<object>} A collection of matched nodes.
579
580
  */
580
- _matchAnPlusB = (ast, node, nthName, opt = {}) => {
581
+ _matchAnPlusB = (ast, node, nthName, opt) => {
581
582
  const {
582
583
  nth: { a, b, name: nthIdentName },
583
584
  selector
@@ -662,8 +663,8 @@ export class Finder {
662
663
  leaves: twigLeaves
663
664
  };
664
665
  opt.dir = DIR_NEXT;
665
- const nodes = this._matchCombinator(twig, node, opt);
666
- if (nodes.size) {
666
+ const nodes = this._collectCombinatorMatches(twig, node, opt, []);
667
+ if (nodes.length) {
667
668
  if (leaves.length) {
668
669
  let bool = false;
669
670
  for (const nextNode of nodes) {
@@ -688,7 +689,7 @@ export class Finder {
688
689
  * @param {object} [opt] - Options.
689
690
  * @returns {?object} The matched node.
690
691
  */
691
- _evaluateHasPseudo = (astData, node, opt) => {
692
+ _evaluateHasPseudo = (astData, node, opt = {}) => {
692
693
  const { branches } = astData;
693
694
  let bool = false;
694
695
  const l = branches.length;
@@ -763,10 +764,7 @@ export class Finder {
763
764
  const arr = [];
764
765
  opt.dir = DIR_PREV;
765
766
  for (const nextNode of nextNodes) {
766
- const m = this._matchCombinator(twig, nextNode, opt);
767
- if (m.size) {
768
- arr.push(...m);
769
- }
767
+ this._collectCombinatorMatches(twig, nextNode, opt, arr);
770
768
  }
771
769
  if (arr.length) {
772
770
  if (j === 0) {
@@ -802,6 +800,8 @@ export class Finder {
802
800
  * @param {object} ast - The AST.
803
801
  * @param {object} node - The Element node.
804
802
  * @param {object} [opt] - Options.
803
+ * @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
804
+ * @param {boolean} [opt.warn] - If true, console warnings are enabled.
805
805
  * @returns {Set.<object>} A collection of matched nodes.
806
806
  */
807
807
  _matchPseudoClassSelector(ast, node, opt = {}) {
@@ -1851,10 +1851,10 @@ export class Finder {
1851
1851
  * @private
1852
1852
  * @param {object} ast - The AST.
1853
1853
  * @param {object} node - The Element node.
1854
- * @param {object} [opt] - Options.
1854
+ * @param {object} opt - Options.
1855
1855
  * @returns {Set.<object>} A collection of matched nodes.
1856
1856
  */
1857
- _matchSelectorForElement = (ast, node, opt = {}) => {
1857
+ _matchSelectorForElement = (ast, node, opt) => {
1858
1858
  const { type: astType } = ast;
1859
1859
  const astName = unescapeSelector(ast.name);
1860
1860
  const matched = new Set();
@@ -1889,7 +1889,7 @@ export class Finder {
1889
1889
  // PS_ELEMENT_SELECTOR is handled by default.
1890
1890
  default: {
1891
1891
  try {
1892
- if (opt.check) {
1892
+ if (this.#check) {
1893
1893
  const css = generateCSS(ast);
1894
1894
  this.#pseudoElement.push(css);
1895
1895
  matched.add(node);
@@ -1934,10 +1934,10 @@ export class Finder {
1934
1934
  * @private
1935
1935
  * @param {object} ast - The AST.
1936
1936
  * @param {object} node - The Document, DocumentFragment, or Element node.
1937
- * @param {object} [opt] - Options.
1937
+ * @param {object} opt - Options.
1938
1938
  * @returns {Set.<object>} A collection of matched nodes.
1939
1939
  */
1940
- _matchSelector = (ast, node, opt = {}) => {
1940
+ _matchSelector = (ast, node, opt) => {
1941
1941
  if (node.nodeType === ELEMENT_NODE) {
1942
1942
  return this._matchSelectorForElement(ast, node, opt);
1943
1943
  }
@@ -1956,10 +1956,10 @@ export class Finder {
1956
1956
  * @private
1957
1957
  * @param {Array.<object>} leaves - The AST leaves.
1958
1958
  * @param {object} node - The node.
1959
- * @param {object} [opt] - Options.
1959
+ * @param {object} opt - Options.
1960
1960
  * @returns {boolean} The result.
1961
1961
  */
1962
- _matchLeaves = (leaves, node, opt = {}) => {
1962
+ _matchLeaves = (leaves, node, opt) => {
1963
1963
  const results = this.#invalidate ? this.#invalidateResults : this.#results;
1964
1964
  let result = results.get(leaves);
1965
1965
  if (result && result.has(node)) {
@@ -2012,10 +2012,10 @@ export class Finder {
2012
2012
  * @private
2013
2013
  * @param {object} baseNode - The base Element node or Element.shadowRoot.
2014
2014
  * @param {Array.<object>} leaves - The AST leaves.
2015
- * @param {object} [opt] - Options.
2015
+ * @param {object} opt - Options.
2016
2016
  * @returns {Set.<object>} A collection of matched nodes.
2017
2017
  */
2018
- _traverseAllDescendants = (baseNode, leaves, opt = {}) => {
2018
+ _traverseAllDescendants = (baseNode, leaves, opt) => {
2019
2019
  const walker = this._createTreeWalker(baseNode);
2020
2020
  traverseNode(baseNode, walker);
2021
2021
  let currentNode = walker.firstChild();
@@ -2034,10 +2034,10 @@ export class Finder {
2034
2034
  * @private
2035
2035
  * @param {Array.<object>} leaves - The AST leaves.
2036
2036
  * @param {object} baseNode - The base Element node or Element.shadowRoot.
2037
- * @param {object} [opt] - Options.
2037
+ * @param {object} opt - Options.
2038
2038
  * @returns {Set.<object>} A collection of matched nodes.
2039
2039
  */
2040
- _findDescendantNodes = (leaves, baseNode, opt = {}) => {
2040
+ _findDescendantNodes = (leaves, baseNode, opt) => {
2041
2041
  const [leaf, ...filterLeaves] = leaves;
2042
2042
  const { type: leafType } = leaf;
2043
2043
  switch (leafType) {
@@ -2080,108 +2080,85 @@ export class Finder {
2080
2080
  };
2081
2081
 
2082
2082
  /**
2083
- * Matches the descendant combinator ' '.
2083
+ * Collects combinator matches into an array without creating intermediate sets.
2084
2084
  * @private
2085
2085
  * @param {object} twig - The twig object.
2086
2086
  * @param {object} node - The Element node.
2087
2087
  * @param {object} [opt] - Options.
2088
- * @returns {Set.<object>} A collection of matched nodes.
2088
+ * @param {string} [opt.dir] - The find direction.
2089
+ * @param {Array.<object>} matched - The collector array.
2090
+ * @returns {Array.<object>} The collector array.
2089
2091
  */
2090
- _matchDescendantCombinator = (twig, node, opt = {}) => {
2091
- const { leaves } = twig;
2092
- const { parentNode } = node;
2092
+ _collectCombinatorMatches = (twig, node, opt = {}, matched = []) => {
2093
+ const {
2094
+ combo: { name: comboName },
2095
+ leaves
2096
+ } = twig;
2093
2097
  const { dir } = opt;
2094
- if (dir === DIR_NEXT) {
2095
- return this._findDescendantNodes(leaves, node, opt);
2096
- }
2097
- // DIR_PREV
2098
- const ancestors = [];
2099
- let refNode = parentNode;
2100
- while (refNode) {
2101
- if (this._matchLeaves(leaves, refNode, opt)) {
2102
- ancestors.push(refNode);
2098
+ switch (comboName) {
2099
+ case '+': {
2100
+ const refNode =
2101
+ dir === DIR_NEXT
2102
+ ? node.nextElementSibling
2103
+ : node.previousElementSibling;
2104
+ if (refNode && this._matchLeaves(leaves, refNode, opt)) {
2105
+ matched.push(refNode);
2106
+ }
2107
+ break;
2103
2108
  }
2104
- refNode = refNode.parentNode;
2105
- }
2106
- if (ancestors.length) {
2107
- // Reverse to maintain document order.
2108
- return new Set(ancestors.reverse());
2109
- }
2110
- return new Set();
2111
- };
2112
-
2113
- /**
2114
- * Matches the child combinator '>'.
2115
- * @private
2116
- * @param {object} twig - The twig object.
2117
- * @param {object} node - The Element node.
2118
- * @param {object} [opt] - Options.
2119
- * @returns {Set.<object>} A collection of matched nodes.
2120
- */
2121
- _matchChildCombinator = (twig, node, opt = {}) => {
2122
- const { leaves } = twig;
2123
- const { dir } = opt;
2124
- const { parentNode } = node;
2125
- const matched = new Set();
2126
- if (dir === DIR_NEXT) {
2127
- let refNode = node.firstElementChild;
2128
- while (refNode) {
2129
- if (this._matchLeaves(leaves, refNode, opt)) {
2130
- matched.add(refNode);
2109
+ case '~': {
2110
+ let refNode =
2111
+ dir === DIR_NEXT
2112
+ ? node.nextElementSibling
2113
+ : node.previousElementSibling;
2114
+ while (refNode) {
2115
+ if (this._matchLeaves(leaves, refNode, opt)) {
2116
+ matched.push(refNode);
2117
+ }
2118
+ refNode =
2119
+ dir === DIR_NEXT
2120
+ ? refNode.nextElementSibling
2121
+ : refNode.previousElementSibling;
2131
2122
  }
2132
- refNode = refNode.nextElementSibling;
2123
+ break;
2133
2124
  }
2134
- } else {
2135
- // DIR_PREV
2136
- if (parentNode && this._matchLeaves(leaves, parentNode, opt)) {
2137
- matched.add(parentNode);
2125
+ case '>': {
2126
+ if (dir === DIR_NEXT) {
2127
+ let refNode = node.firstElementChild;
2128
+ while (refNode) {
2129
+ if (this._matchLeaves(leaves, refNode, opt)) {
2130
+ matched.push(refNode);
2131
+ }
2132
+ refNode = refNode.nextElementSibling;
2133
+ }
2134
+ } else {
2135
+ const { parentNode } = node;
2136
+ if (parentNode && this._matchLeaves(leaves, parentNode, opt)) {
2137
+ matched.push(parentNode);
2138
+ }
2139
+ }
2140
+ break;
2138
2141
  }
2139
- }
2140
- return matched;
2141
- };
2142
-
2143
- /**
2144
- * Matches the adjacent sibling combinator '+'.
2145
- * @private
2146
- * @param {object} twig - The twig object.
2147
- * @param {object} node - The Element node.
2148
- * @param {object} [opt] - Options.
2149
- * @returns {Set.<object>} A collection of matched nodes.
2150
- */
2151
- _matchAdjacentSiblingCombinator = (twig, node, opt = {}) => {
2152
- const { leaves } = twig;
2153
- const { dir } = opt;
2154
- const matched = new Set();
2155
- const refNode =
2156
- dir === DIR_NEXT ? node.nextElementSibling : node.previousElementSibling;
2157
- if (refNode && this._matchLeaves(leaves, refNode, opt)) {
2158
- matched.add(refNode);
2159
- }
2160
- return matched;
2161
- };
2162
-
2163
- /**
2164
- * Matches the general sibling combinator '~'.
2165
- * @private
2166
- * @param {object} twig - The twig object.
2167
- * @param {object} node - The Element node.
2168
- * @param {object} [opt] - Options.
2169
- * @returns {Set.<object>} A collection of matched nodes.
2170
- */
2171
- _matchGeneralSiblingCombinator = (twig, node, opt = {}) => {
2172
- const { leaves } = twig;
2173
- const { dir } = opt;
2174
- const matched = new Set();
2175
- let refNode =
2176
- dir === DIR_NEXT ? node.nextElementSibling : node.previousElementSibling;
2177
- while (refNode) {
2178
- if (this._matchLeaves(leaves, refNode, opt)) {
2179
- matched.add(refNode);
2142
+ case ' ':
2143
+ default: {
2144
+ if (dir === DIR_NEXT) {
2145
+ for (const refNode of this._findDescendantNodes(leaves, node, opt)) {
2146
+ matched.push(refNode);
2147
+ }
2148
+ } else {
2149
+ const ancestors = [];
2150
+ let refNode = node.parentNode;
2151
+ while (refNode) {
2152
+ if (this._matchLeaves(leaves, refNode, opt)) {
2153
+ ancestors.push(refNode);
2154
+ }
2155
+ refNode = refNode.parentNode;
2156
+ }
2157
+ if (ancestors.length) {
2158
+ matched.push(...ancestors.reverse());
2159
+ }
2160
+ }
2180
2161
  }
2181
- refNode =
2182
- dir === DIR_NEXT
2183
- ? refNode.nextElementSibling
2184
- : refNode.previousElementSibling;
2185
2162
  }
2186
2163
  return matched;
2187
2164
  };
@@ -2191,44 +2168,26 @@ export class Finder {
2191
2168
  * @private
2192
2169
  * @param {object} twig - The twig object.
2193
2170
  * @param {object} node - The Element node.
2194
- * @param {object} [opt] - Options.
2171
+ * @param {object} opt - Options.
2195
2172
  * @returns {Set.<object>} A collection of matched nodes.
2196
2173
  */
2197
- _matchCombinator = (twig, node, opt = {}) => {
2198
- const {
2199
- combo: { name: comboName }
2200
- } = twig;
2201
- switch (comboName) {
2202
- case '+': {
2203
- return this._matchAdjacentSiblingCombinator(twig, node, opt);
2204
- }
2205
- case '~': {
2206
- return this._matchGeneralSiblingCombinator(twig, node, opt);
2207
- }
2208
- case '>': {
2209
- return this._matchChildCombinator(twig, node, opt);
2210
- }
2211
- case ' ':
2212
- default: {
2213
- return this._matchDescendantCombinator(twig, node, opt);
2214
- }
2215
- }
2216
- };
2174
+ _matchCombinator = (twig, node, opt) =>
2175
+ new Set(this._collectCombinatorMatches(twig, node, opt));
2217
2176
 
2218
2177
  /**
2219
2178
  * Traverses with a TreeWalker and collects nodes matching the leaves.
2220
2179
  * @private
2221
2180
  * @param {TreeWalker} walker - The TreeWalker instance to use.
2222
2181
  * @param {Array} leaves - The AST leaves to match against.
2223
- * @param {object} options - Traversal options.
2224
- * @param {Node} options.startNode - The node to start traversal from.
2225
- * @param {string} options.targetType - The type of target ('all' or 'first').
2226
- * @param {Node} [options.boundaryNode] - The node to stop traversal at.
2227
- * @param {boolean} [options.force] - Force traversal to the next node.
2182
+ * @param {object} [opt] - Traversal options.
2183
+ * @param {Node} [opt.boundaryNode] - The node to stop traversal at.
2184
+ * @param {boolean} [opt.force] - Force traversal to the next node.
2185
+ * @param {Node} [opt.startNode] - The node to start traversal from.
2186
+ * @param {string} [opt.targetType] - The type of target ('all' or 'first').
2228
2187
  * @returns {Array.<Node>} An array of matched nodes.
2229
2188
  */
2230
- _traverseAndCollectNodes = (walker, leaves, options) => {
2231
- const { boundaryNode, force, startNode, targetType } = options;
2189
+ _traverseAndCollectNodes = (walker, leaves, opt = {}) => {
2190
+ const { boundaryNode, force, startNode, targetType } = opt;
2232
2191
  const collectedNodes = [];
2233
2192
  let currentNode = traverseNode(startNode, walker, !!force);
2234
2193
  if (!currentNode) {
@@ -2275,7 +2234,7 @@ export class Finder {
2275
2234
  * @private
2276
2235
  * @param {Array.<object>} leaves - The AST leaves.
2277
2236
  * @param {object} node - The node to start from.
2278
- * @param {object} opt - Options.
2237
+ * @param {object} [opt] - Options.
2279
2238
  * @param {boolean} [opt.force] - If true, traverses only to the next node.
2280
2239
  * @param {string} [opt.targetType] - The target type.
2281
2240
  * @returns {Array.<object>} A collection of matched nodes.
@@ -2298,7 +2257,7 @@ export class Finder {
2298
2257
  * @private
2299
2258
  * @param {Array.<object>} leaves - The AST leaves.
2300
2259
  * @param {object} node - The node to start from.
2301
- * @param {object} opt - Options.
2260
+ * @param {object} [opt] - Options.
2302
2261
  * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2303
2262
  * @returns {Array.<object>} A collection of matched nodes.
2304
2263
  */
@@ -2323,12 +2282,13 @@ export class Finder {
2323
2282
  * Matches the node itself.
2324
2283
  * @private
2325
2284
  * @param {Array} leaves - The AST leaves.
2326
- * @param {boolean} check - Indicates if running in internal check().
2327
2285
  * @returns {Array} An array containing [nodes, filtered, pseudoElement].
2328
2286
  */
2329
- _matchSelf = (leaves, check = false) => {
2330
- const options = { check, warn: this.#warn };
2331
- const matched = this._matchLeaves(leaves, this.#node, options);
2287
+ _matchSelf = leaves => {
2288
+ const matched = this._matchLeaves(leaves, this.#node, {
2289
+ check: this.#check,
2290
+ warn: this.#warn
2291
+ });
2332
2292
  const nodes = matched ? [this.#node] : [];
2333
2293
  return [nodes, matched, this.#pseudoElement];
2334
2294
  };
@@ -2337,21 +2297,22 @@ export class Finder {
2337
2297
  * Finds lineal nodes (self and ancestors).
2338
2298
  * @private
2339
2299
  * @param {Array} leaves - The AST leaves.
2340
- * @param {object} opt - Options.
2300
+ * @param {object} [opt] - Options.
2301
+ * @param {boolean} [opt.complex] - If true, the selector is complex.
2341
2302
  * @returns {Array} An array containing [nodes, filtered].
2342
2303
  */
2343
- _findLineal = (leaves, opt) => {
2304
+ _findLineal = (leaves, opt = {}) => {
2344
2305
  const { complex } = opt;
2345
2306
  const nodes = [];
2346
- const options = { warn: this.#warn };
2347
- const selfMatched = this._matchLeaves(leaves, this.#node, options);
2307
+ const matchOpts = { warn: this.#warn };
2308
+ const selfMatched = this._matchLeaves(leaves, this.#node, matchOpts);
2348
2309
  if (selfMatched) {
2349
2310
  nodes.push(this.#node);
2350
2311
  }
2351
2312
  if (!selfMatched || complex) {
2352
2313
  let currentNode = this.#node.parentNode;
2353
2314
  while (currentNode) {
2354
- if (this._matchLeaves(leaves, currentNode, options)) {
2315
+ if (this._matchLeaves(leaves, currentNode, matchOpts)) {
2355
2316
  nodes.push(currentNode);
2356
2317
  }
2357
2318
  currentNode = currentNode.parentNode;
@@ -2376,7 +2337,7 @@ export class Finder {
2376
2337
  const css = generateCSS(leaf);
2377
2338
  this.#pseudoElement.push(css);
2378
2339
  if (filterLeaves.length) {
2379
- [nodes, filtered] = this._matchSelf(filterLeaves, this.#check);
2340
+ [nodes, filtered] = this._matchSelf(filterLeaves);
2380
2341
  } else {
2381
2342
  nodes.push(this.#node);
2382
2343
  filtered = true;
@@ -2392,17 +2353,19 @@ export class Finder {
2392
2353
  * @private
2393
2354
  * @param {object} twig - The current twig from the AST branch.
2394
2355
  * @param {string} targetType - The type of target to find.
2395
- * @param {object} opt - Additional options for finding nodes.
2356
+ * @param {object} [opt] - Options.
2357
+ * @param {boolean} [opt.complex] - If true, the selector is complex.
2358
+ * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2396
2359
  * @returns {object} The result { nodes, filtered, pending }.
2397
2360
  */
2398
- _findEntryNodesForId = (twig, targetType, opt) => {
2361
+ _findEntryNodesForId = (twig, targetType, opt = {}) => {
2399
2362
  const { leaves } = twig;
2400
2363
  const [leaf, ...filterLeaves] = leaves;
2401
2364
  const { complex, precede } = opt;
2402
2365
  let nodes = [];
2403
2366
  let filtered = false;
2404
2367
  if (targetType === TARGET_SELF) {
2405
- [nodes, filtered] = this._matchSelf(leaves, this.#check);
2368
+ [nodes, filtered] = this._matchSelf(leaves);
2406
2369
  } else if (targetType === TARGET_LINEAL) {
2407
2370
  [nodes, filtered] = this._findLineal(leaves, { complex });
2408
2371
  } else if (
@@ -2433,15 +2396,17 @@ export class Finder {
2433
2396
  * @private
2434
2397
  * @param {Array.<object>} leaves - The AST leaves for the selector.
2435
2398
  * @param {string} targetType - The type of target to find.
2436
- * @param {object} opt - Additional options for finding nodes.
2399
+ * @param {object} [opt] - Options.
2400
+ * @param {boolean} [opt.complex] - If true, the selector is complex.
2401
+ * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2437
2402
  * @returns {object} The result { nodes, filtered, pending }.
2438
2403
  */
2439
- _findEntryNodesForClass = (leaves, targetType, opt) => {
2404
+ _findEntryNodesForClass = (leaves, targetType, opt = {}) => {
2440
2405
  const { complex, precede } = opt;
2441
2406
  let nodes = [];
2442
2407
  let filtered = false;
2443
2408
  if (targetType === TARGET_SELF) {
2444
- [nodes, filtered] = this._matchSelf(leaves, this.#check);
2409
+ [nodes, filtered] = this._matchSelf(leaves);
2445
2410
  } else if (targetType === TARGET_LINEAL) {
2446
2411
  [nodes, filtered] = this._findLineal(leaves, { complex });
2447
2412
  } else {
@@ -2456,15 +2421,17 @@ export class Finder {
2456
2421
  * @private
2457
2422
  * @param {Array.<object>} leaves - The AST leaves for the selector.
2458
2423
  * @param {string} targetType - The type of target to find.
2459
- * @param {object} opt - Additional options for finding nodes.
2424
+ * @param {object} [opt] - Options.
2425
+ * @param {boolean} [opt.complex] - If true, the selector is complex.
2426
+ * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2460
2427
  * @returns {object} The result { nodes, filtered, pending }.
2461
2428
  */
2462
- _findEntryNodesForType = (leaves, targetType, opt) => {
2429
+ _findEntryNodesForType = (leaves, targetType, opt = {}) => {
2463
2430
  const { complex, precede } = opt;
2464
2431
  let nodes = [];
2465
2432
  let filtered = false;
2466
2433
  if (targetType === TARGET_SELF) {
2467
- [nodes, filtered] = this._matchSelf(leaves, this.#check);
2434
+ [nodes, filtered] = this._matchSelf(leaves);
2468
2435
  } else if (targetType === TARGET_LINEAL) {
2469
2436
  [nodes, filtered] = this._findLineal(leaves, { complex });
2470
2437
  } else {
@@ -2479,10 +2446,12 @@ export class Finder {
2479
2446
  * @private
2480
2447
  * @param {object} twig - The current twig from the AST branch.
2481
2448
  * @param {string} targetType - The type of target to find.
2482
- * @param {object} opt - Additional options for finding nodes.
2449
+ * @param {object} [opt] - Options.
2450
+ * @param {boolean} [opt.complex] - If true, the selector is complex.
2451
+ * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2483
2452
  * @returns {object} The result { nodes, filtered, pending }.
2484
2453
  */
2485
- _findEntryNodesForOther = (twig, targetType, opt) => {
2454
+ _findEntryNodesForOther = (twig, targetType, opt = {}) => {
2486
2455
  const { leaves } = twig;
2487
2456
  const [leaf, ...filterLeaves] = leaves;
2488
2457
  const { complex, precede } = opt;
@@ -2536,7 +2505,7 @@ export class Finder {
2536
2505
  }
2537
2506
  }
2538
2507
  } else if (targetType === TARGET_SELF) {
2539
- [nodes, filtered] = this._matchSelf(leaves, this.#check);
2508
+ [nodes, filtered] = this._matchSelf(leaves);
2540
2509
  } else if (targetType === TARGET_LINEAL) {
2541
2510
  [nodes, filtered] = this._findLineal(leaves, { complex });
2542
2511
  } else if (targetType === TARGET_FIRST) {
@@ -2792,15 +2761,13 @@ export class Finder {
2792
2761
  */
2793
2762
  _getCombinedNodes = (twig, nodes, dir) => {
2794
2763
  const arr = [];
2795
- const options = {
2796
- dir,
2797
- warn: this.#warn
2798
- };
2799
2764
  for (const node of nodes) {
2800
- const matched = this._matchCombinator(twig, node, options);
2801
- if (matched.size) {
2802
- arr.push(...matched);
2803
- }
2765
+ this._collectCombinatorMatches(
2766
+ twig,
2767
+ node,
2768
+ { dir, warn: this.#warn },
2769
+ arr
2770
+ );
2804
2771
  }
2805
2772
  return arr;
2806
2773
  };
@@ -2810,21 +2777,24 @@ export class Finder {
2810
2777
  * @private
2811
2778
  * @param {Array} branch - The branch.
2812
2779
  * @param {Set.<object>} nodes - A collection of Element nodes.
2813
- * @param {object} opt - Options.
2814
- * @param {object} opt.combo - The combo object.
2815
- * @param {number} opt.index - The index.
2780
+ * @param {object} [opt] - Options.
2781
+ * @param {object} [opt.combo] - The combo object.
2782
+ * @param {number} [opt.index] - The index.
2816
2783
  * @returns {?object} The matched node.
2817
2784
  */
2818
- _matchNodeNext = (branch, nodes, opt) => {
2785
+ _matchNodeNext = (branch, nodes, opt = {}) => {
2819
2786
  const { combo, index } = opt;
2820
2787
  const { combo: nextCombo, leaves } = branch[index];
2821
2788
  const twig = {
2822
2789
  combo,
2823
2790
  leaves
2824
2791
  };
2825
- const nextNodes = new Set(this._getCombinedNodes(twig, nodes, DIR_NEXT));
2826
- if (nextNodes.size) {
2792
+ const nextNodes = this._getCombinedNodes(twig, nodes, DIR_NEXT);
2793
+ if (nextNodes.length) {
2827
2794
  if (index === branch.length - 1) {
2795
+ if (nextNodes.length === 1) {
2796
+ return nextNodes[0];
2797
+ }
2828
2798
  const [nextNode] = sortNodes(nextNodes);
2829
2799
  return nextNode;
2830
2800
  }
@@ -2841,16 +2811,15 @@ export class Finder {
2841
2811
  * @private
2842
2812
  * @param {Array} branch - The branch.
2843
2813
  * @param {object} node - The Element node.
2844
- * @param {object} opt - Options.
2845
- * @param {number} opt.index - The index.
2814
+ * @param {object} [opt] - Options.
2815
+ * @param {number} [opt.index] - The index.
2846
2816
  * @returns {?object} The node.
2847
2817
  */
2848
- _matchNodePrev = (branch, node, opt) => {
2818
+ _matchNodePrev = (branch, node, opt = {}) => {
2849
2819
  const { index } = opt;
2850
2820
  const twig = branch[index];
2851
- const nodes = new Set([node]);
2852
- const nextNodes = new Set(this._getCombinedNodes(twig, nodes, DIR_PREV));
2853
- if (nextNodes.size) {
2821
+ const nextNodes = this._getCombinedNodes(twig, [node], DIR_PREV);
2822
+ if (nextNodes.length) {
2854
2823
  if (index === 0) {
2855
2824
  return node;
2856
2825
  }
@@ -2887,7 +2856,7 @@ export class Finder {
2887
2856
  const { combo: firstCombo } = branch[0];
2888
2857
  for (const node of entryNodes) {
2889
2858
  let combo = firstCombo;
2890
- let nextNodes = new Set([node]);
2859
+ let nextNodes = [node];
2891
2860
  for (let j = 1; j < branchLen; j++) {
2892
2861
  const { combo: nextCombo, leaves } = branch[j];
2893
2862
  const twig = { combo, leaves };
@@ -2899,10 +2868,8 @@ export class Finder {
2899
2868
  }
2900
2869
  }
2901
2870
  combo = nextCombo;
2902
- nextNodes = new Set(nodesArr);
2871
+ nextNodes = nodesArr;
2903
2872
  } else {
2904
- // No further matches down this path.
2905
- nextNodes.clear();
2906
2873
  break;
2907
2874
  }
2908
2875
  }
@@ -2910,7 +2877,7 @@ export class Finder {
2910
2877
  // DIR_PREV
2911
2878
  } else {
2912
2879
  for (const node of entryNodes) {
2913
- let nextNodes = new Set([node]);
2880
+ let nextNodes = [node];
2914
2881
  for (let j = lastIndex - 1; j >= 0; j--) {
2915
2882
  const twig = branch[j];
2916
2883
  const nodesArr = this._getCombinedNodes(twig, nextNodes, dir);
@@ -2919,10 +2886,8 @@ export class Finder {
2919
2886
  if (j === 0) {
2920
2887
  matchedNodes.add(node);
2921
2888
  }
2922
- nextNodes = new Set(nodesArr);
2889
+ nextNodes = nodesArr;
2923
2890
  } else {
2924
- // No further matches down this path.
2925
- nextNodes.clear();
2926
2891
  break;
2927
2892
  }
2928
2893
  }
@@ -3152,7 +3117,6 @@ export class Finder {
3152
3117
  * @returns {object} The AST for the selector.
3153
3118
  */
3154
3119
  getAST = selector => {
3155
- // TBD: get cached AST if possible.
3156
3120
  return parseSelector(selector);
3157
3121
  };
3158
3122
  }
package/src/js/matcher.js CHANGED
@@ -34,7 +34,6 @@ const KEYS_FORM_PS_DISABLED = new Set([
34
34
  ]);
35
35
  const KEYS_INPUT_EDIT = new Set(INPUT_EDIT);
36
36
  const REG_LANG_VALID = new RegExp(`^(?:\\*-)?${ALPHA_NUM}${LANG_PART}$`, 'i');
37
- const REG_TAG_NAME = /[A-Z][\\w-]*/i;
38
37
 
39
38
  /**
40
39
  * Validates a pseudo-element selector.
@@ -315,9 +314,8 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
315
314
  return false;
316
315
  }
317
316
  // Determine case sensitivity based on document type and flags.
318
- const contentType = node.ownerDocument.contentType;
319
317
  let caseInsensitive;
320
- if (contentType === 'text/html') {
318
+ if (node.ownerDocument.contentType === 'text/html') {
321
319
  if (typeof astFlags === 'string' && /^s$/i.test(astFlags)) {
322
320
  caseInsensitive = false;
323
321
  } else {
@@ -345,6 +343,7 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
345
343
  itemName = itemName.toLowerCase();
346
344
  itemValue = itemValue.toLowerCase();
347
345
  }
346
+ const colonIdx = itemName.indexOf(':');
348
347
  switch (astPrefix) {
349
348
  case '': {
350
349
  if (astLocalName === itemName) {
@@ -353,9 +352,10 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
353
352
  break;
354
353
  }
355
354
  case '*': {
356
- if (itemName.indexOf(':') > -1) {
357
- const [, ...restItemName] = itemName.split(':');
358
- const itemLocalName = restItemName.join(':').replace(/^:/, '');
355
+ if (colonIdx > -1) {
356
+ const itemLocalName = itemName
357
+ .substring(colonIdx + 1)
358
+ .replace(/^:/, '');
359
359
  if (itemLocalName === astLocalName) {
360
360
  attrValues.add(itemValue);
361
361
  }
@@ -376,9 +376,11 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
376
376
  globalObject
377
377
  );
378
378
  }
379
- if (itemName.indexOf(':') > -1) {
380
- const [itemPrefix, ...restItemName] = itemName.split(':');
381
- const itemLocalName = restItemName.join(':').replace(/^:/, '');
379
+ if (colonIdx > -1) {
380
+ const itemPrefix = itemName.substring(0, colonIdx);
381
+ const itemLocalName = itemName
382
+ .substring(colonIdx + 1)
383
+ .replace(/^:/, '');
382
384
  // Ignore the 'xml:lang' attribute.
383
385
  if (itemPrefix === 'xml' && itemLocalName === 'lang') {
384
386
  continue;
@@ -402,9 +404,12 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
402
404
  itemName = itemName.toLowerCase();
403
405
  itemValue = itemValue.toLowerCase();
404
406
  }
405
- if (itemName.indexOf(':') > -1) {
406
- const [itemPrefix, ...restItemName] = itemName.split(':');
407
- const itemLocalName = restItemName.join(':').replace(/^:/, '');
407
+ const colonIdx = itemName.indexOf(':');
408
+ if (colonIdx > -1) {
409
+ const itemPrefix = itemName.substring(0, colonIdx);
410
+ const itemLocalName = itemName
411
+ .substring(colonIdx + 1)
412
+ .replace(/^:/, '');
408
413
  // The attribute is starting with ':'.
409
414
  if (!itemPrefix && astAttrName === `:${itemLocalName}`) {
410
415
  attrValues.add(itemValue);
@@ -427,15 +432,15 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
427
432
  let attrValue;
428
433
  if (astIdentValue) {
429
434
  if (caseInsensitive) {
430
- attrValue = astIdentValue.toLowerCase();
435
+ attrValue = astIdentValue.toLowerCase().replace(/\\(?!\\)/g, '');
431
436
  } else {
432
- attrValue = astIdentValue;
437
+ attrValue = astIdentValue.replace(/\\(?!\\)/g, '');
433
438
  }
434
439
  } else if (astStringValue) {
435
440
  if (caseInsensitive) {
436
- attrValue = astStringValue.toLowerCase();
441
+ attrValue = astStringValue.toLowerCase().replace(/\\(?!\\)/g, '');
437
442
  } else {
438
- attrValue = astStringValue;
443
+ attrValue = astStringValue.replace(/\\(?!\\)/g, '');
439
444
  }
440
445
  } else if (astStringValue === '') {
441
446
  attrValue = astStringValue;
@@ -521,39 +526,39 @@ export const matchTypeSelector = (ast, node, opt = {}) => {
521
526
  astName,
522
527
  node
523
528
  );
529
+ const firstChar = localName.charCodeAt(0);
530
+ const isAlphabet =
531
+ (firstChar >= 65 && firstChar <= 90) ||
532
+ (firstChar >= 97 && firstChar <= 122);
524
533
  if (
525
534
  node.ownerDocument.contentType === 'text/html' &&
526
535
  (!namespaceURI || namespaceURI === 'http://www.w3.org/1999/xhtml') &&
527
- REG_TAG_NAME.test(localName)
536
+ isAlphabet
528
537
  ) {
529
538
  astPrefix = astPrefix.toLowerCase();
530
539
  astLocalName = astLocalName.toLowerCase();
531
540
  }
532
541
  let nodePrefix;
533
542
  let nodeLocalName;
534
- // just in case that the namespaced content is parsed as text/html
535
- if (localName.indexOf(':') > -1) {
536
- [nodePrefix, nodeLocalName] = localName.split(':');
543
+ const colonIdx = localName.indexOf(':');
544
+ if (colonIdx > -1) {
545
+ nodePrefix = localName.substring(0, colonIdx);
546
+ nodeLocalName = localName.substring(colonIdx + 1);
537
547
  } else {
538
548
  nodePrefix = prefix || '';
539
549
  nodeLocalName = localName;
540
550
  }
551
+ const isUniversal = astLocalName === '*';
541
552
  switch (astPrefix) {
542
553
  case '': {
543
- if (
554
+ return (
544
555
  !nodePrefix &&
545
556
  !namespaceURI &&
546
- (astLocalName === '*' || astLocalName === nodeLocalName)
547
- ) {
548
- return true;
549
- }
550
- return false;
557
+ (isUniversal || astLocalName === nodeLocalName)
558
+ );
551
559
  }
552
560
  case '*': {
553
- if (astLocalName === '*' || astLocalName === nodeLocalName) {
554
- return true;
555
- }
556
- return false;
561
+ return isUniversal || astLocalName === nodeLocalName;
557
562
  }
558
563
  default: {
559
564
  if (!check) {
@@ -570,10 +575,7 @@ export const matchTypeSelector = (ast, node, opt = {}) => {
570
575
  const astNS = node.lookupNamespaceURI(astPrefix);
571
576
  const nodeNS = node.lookupNamespaceURI(nodePrefix);
572
577
  if (astNS === nodeNS && astPrefix === nodePrefix) {
573
- if (astLocalName === '*' || astLocalName === nodeLocalName) {
574
- return true;
575
- }
576
- return false;
578
+ return isUniversal || astLocalName === nodeLocalName;
577
579
  } else if (!forgive && !astNS) {
578
580
  throw generateException(
579
581
  `Undeclared namespace ${astPrefix}`,
package/src/js/parser.js CHANGED
@@ -170,8 +170,7 @@ export const parseSelector = sel => {
170
170
  }
171
171
  try {
172
172
  return cssTree.parse(selector, {
173
- context: 'selectorList',
174
- parseCustomProperty: true
173
+ context: 'selectorList'
175
174
  });
176
175
  } catch (e) {
177
176
  const { message } = e;
package/src/js/utility.js CHANGED
@@ -32,7 +32,6 @@ import {
32
32
  RULE,
33
33
  SCOPE,
34
34
  SELECTOR_LIST,
35
- SIBLING,
36
35
  TARGET_ALL,
37
36
  TARGET_FIRST,
38
37
  TEXT_NODE,
@@ -59,11 +58,11 @@ const KEYS_NODE_FOCUSABLE_SVG = new Set([
59
58
  'symbol',
60
59
  'title'
61
60
  ]);
61
+ const REG_ATTR_SIMPLE = /^\[[A-Z\d-]{1,255}="?[A-Z\d\s-]{1,255}"?\]$/i;
62
62
  const REG_EXCLUDE_BASIC =
63
63
  /[|\\]|::|[^\u0021-\u007F\s]|\[\s*[\w$*=^|~-]+(?:(?:"[\w$*=^|~\s'-]+"|'[\w$*=^|~\s"-]+')?(?:\s+[\w$*=^|~-]+)+|"[^"\]]{1,255}|'[^'\]]{1,255})\s*\]|:(?:is|where)\(\s*\)/;
64
64
  const REG_COMPLEX = new RegExp(`${COMPOUND_I}${COMBO}${COMPOUND_I}`, 'i');
65
65
  const REG_DESCEND = new RegExp(`${COMPOUND_I}${DESCEND}${COMPOUND_I}`, 'i');
66
- const REG_SIBLING = new RegExp(`${COMPOUND_I}${SIBLING}${COMPOUND_I}`, 'i');
67
66
  const REG_LOGIC_COMPLEX = new RegExp(
68
67
  `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPLEX})`
69
68
  );
@@ -1045,7 +1044,7 @@ export const initNwsapi = (window, document) => {
1045
1044
  * @returns {boolean} - True if the selector is valid for nwsapi.
1046
1045
  */
1047
1046
  export const filterSelector = (selector, target) => {
1048
- const isQuerySelectorType = target === TARGET_FIRST || target === TARGET_ALL;
1047
+ const isQuerySelectorAll = target === TARGET_ALL;
1049
1048
  if (
1050
1049
  !selector ||
1051
1050
  typeof selector !== 'string' ||
@@ -1061,6 +1060,13 @@ export const filterSelector = (selector, target) => {
1061
1060
  return false;
1062
1061
  }
1063
1062
  }
1063
+ // Match only simple attribute selector for TARGET_FIRST.
1064
+ if (target === TARGET_FIRST) {
1065
+ if (REG_ATTR_SIMPLE.test(selector)) {
1066
+ return true;
1067
+ }
1068
+ return false;
1069
+ }
1064
1070
  // Exclude various complex or unsupported selectors.
1065
1071
  // - selectors containing '/'
1066
1072
  // - namespaced selectors
@@ -1076,17 +1082,11 @@ export const filterSelector = (selector, target) => {
1076
1082
  }
1077
1083
  // Include pseudo-classes that are known to work correctly.
1078
1084
  if (selector.includes(':')) {
1079
- let complex = false;
1080
- if (target !== isQuerySelectorType) {
1081
- complex = REG_COMPLEX.test(selector);
1082
- }
1083
- if (
1084
- isQuerySelectorType &&
1085
- REG_DESCEND.test(selector) &&
1086
- !REG_SIBLING.test(selector)
1087
- ) {
1085
+ if (isQuerySelectorAll && REG_DESCEND.test(selector)) {
1088
1086
  return false;
1089
- } else if (!isQuerySelectorType && /:has\(/.test(selector)) {
1087
+ }
1088
+ const complex = isQuerySelectorAll ? false : REG_COMPLEX.test(selector);
1089
+ if (!isQuerySelectorAll && /:has\(/.test(selector)) {
1090
1090
  if (!complex || REG_LOGIC_HAS_COMPOUND.test(selector)) {
1091
1091
  return false;
1092
1092
  }
@@ -34,10 +34,7 @@ export class Finder {
34
34
  private _matchLeaves;
35
35
  private _traverseAllDescendants;
36
36
  private _findDescendantNodes;
37
- private _matchDescendantCombinator;
38
- private _matchChildCombinator;
39
- private _matchAdjacentSiblingCombinator;
40
- private _matchGeneralSiblingCombinator;
37
+ private _collectCombinatorMatches;
41
38
  private _matchCombinator;
42
39
  private _traverseAndCollectNodes;
43
40
  private _findPrecede;