@asamuzakjp/dom-selector 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  [![build](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml/badge.svg)](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml)
4
4
  [![CodeQL](https://github.com/asamuzaK/domSelector/actions/workflows/codeql.yml/badge.svg)](https://github.com/asamuzaK/domSelector/actions/workflows/codeql.yml)
5
+ [![npm (scoped)](https://img.shields.io/npm/v/@asamuzakjp/dom-selector)](https://www.npmjs.com/package/@asamuzakjp/dom-selector)
5
6
  <!--
6
- [![npm](https://img.shields.io/npm/v/dom-selector)](https://www.npmjs.com/package/url-sanitizer)
7
7
  [![release](https://img.shields.io/github/v/release/asamuzaK/domSelector)](https://github.com/asamuzaK/domSelector/releases)
8
8
  -->
9
9
 
@@ -14,7 +14,7 @@ Retrieve DOM node from the given CSS selector.
14
14
  ## Install
15
15
 
16
16
  ```console
17
- npm i dom-selector
17
+ npm i @asamuzakjp/dom-selector
18
18
  ```
19
19
 
20
20
 
package/package.json CHANGED
@@ -41,5 +41,5 @@
41
41
  "test": "c8 --reporter=text mocha --exit test/**/*.test.js",
42
42
  "tsc": "npx tsc"
43
43
  },
44
- "version": "0.2.1"
44
+ "version": "0.2.2"
45
45
  }
@@ -4,44 +4,16 @@
4
4
 
5
5
  module.exports = {
6
6
  AN_PLUS_B: 'AnPlusB',
7
- AT_RULE: 'Atrule',
8
- AT_RULE_PRELUDE: 'AtrulePrelude',
9
7
  ATTRIBUTE_SELECTOR: 'AttributeSelector',
10
- BLOCK: 'Block',
11
- BRACKETS: 'Brackets',
12
- CDC: 'CDC',
13
- CDO: 'CDO',
14
8
  CLASS_SELECTOR: 'ClassSelector',
15
9
  COMBINATOR: 'Combinator',
16
- COMMENT: 'Comment',
17
- DECLARATION: 'Declaration',
18
- DECLARATION_LIST: 'DeclarationList',
19
- DIMENSION: 'Dimension',
20
- FUNCTION: 'Function',
21
- HASH: 'Hash',
22
10
  ID_SELECTOR: 'IdSelector',
23
11
  IDENTIFIER: 'Identifier',
24
- MEDIA_FEATURE: 'MediaFeature',
25
- MEDIA_QUERY: 'MediaQuery',
26
- MEDIA_QUERY_LIST: 'MediaQueryList',
27
- NESTING_SELECTOR: 'NestingSelector',
28
12
  NTH: 'Nth',
29
- NUMBER: 'Number',
30
- OPERATOR: 'Operator',
31
- PARENTHESES: 'Parentheses',
32
- PERCENTAGE: 'Percentage',
33
13
  PSEUDO_CLASS_SELECTOR: 'PseudoClassSelector',
34
- PSEUDO_ELEMENT_SELECTOR: 'PseudoElementSelector',
35
- RATIO: 'Ratio',
36
14
  RAW: 'Raw',
37
- RULE: 'Rule',
38
15
  SELECTOR: 'Selector',
39
16
  SELECTOR_LIST: 'SelectorList',
40
17
  STRING: 'String',
41
- STYLE_SHEET: 'StyleSheet',
42
- TYPE_SELECTOR: 'TypeSelector',
43
- UNICODE_RANGE: 'UnicodeRange',
44
- URL: 'Url',
45
- VALUE: 'Value',
46
- WHITESPACE: 'WhiteSpace'
18
+ TYPE_SELECTOR: 'TypeSelector'
47
19
  };
package/src/js/matcher.js CHANGED
@@ -16,7 +16,7 @@ const REG_PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
16
16
 
17
17
  /**
18
18
  * collect nth child
19
- * @param {object} node - element node
19
+ * @param {object} node - Element node
20
20
  * @param {object} opt - options
21
21
  * @param {number} opt.a - a
22
22
  * @param {number} opt.b - b
@@ -44,8 +44,10 @@ const collectNthChild = (node = {}, opt = {}) => {
44
44
  } else {
45
45
  let n = 0;
46
46
  let nth = b - 1;
47
- while (nth < 0) {
48
- nth += (++n * a);
47
+ if (a > 0) {
48
+ while (nth < 0) {
49
+ nth += (++n * a);
50
+ }
49
51
  }
50
52
  let i = 0;
51
53
  while (i < l && nth < l) {
@@ -63,7 +65,7 @@ const collectNthChild = (node = {}, opt = {}) => {
63
65
 
64
66
  /**
65
67
  * collect nth of type
66
- * @param {object} node - element node
68
+ * @param {object} node - Element node
67
69
  * @param {object} opt - options
68
70
  * @param {number} opt.a - a
69
71
  * @param {number} opt.b - b
@@ -102,8 +104,10 @@ const collectNthOfType = (node = {}, opt = {}) => {
102
104
  // :nth-of-type()
103
105
  } else {
104
106
  let nth = b - 1;
105
- while (nth < 0) {
106
- nth += a;
107
+ if (a > 0) {
108
+ while (nth < 0) {
109
+ nth += a;
110
+ }
107
111
  }
108
112
  let i = 0;
109
113
  let j = 0;
@@ -126,25 +130,41 @@ const collectNthOfType = (node = {}, opt = {}) => {
126
130
 
127
131
  /**
128
132
  * match type selector
129
- * @param {object} leaf - ast leaf
130
- * @param {object} node - element node
133
+ * @param {object} ast - AST
134
+ * @param {object} node - Element node
131
135
  * @returns {?object} - matched node
132
136
  */
133
- const matchTypeSelector = (leaf = {}, node = {}) => {
134
- const { name: leafName, type: leafType } = leaf;
135
- const { localName, nodeType, prefix } = node;
137
+ const matchTypeSelector = (ast = {}, node = {}) => {
138
+ const { type: astType } = ast;
139
+ const { localName, nodeType, ownerDocument, prefix } = node;
136
140
  let res;
137
- if (leafType === TYPE_SELECTOR && nodeType === ELEMENT_NODE) {
138
- // namespaced
139
- if (/\|/.test(leafName)) {
140
- const [leafPrefix, leafLocalName] = leafName.split('|');
141
- if (((leafPrefix === '' && !prefix) || // |E
142
- (leafPrefix === '*') || // *|E
143
- (leafPrefix === prefix)) && // ns|E
144
- (leafLocalName === '*' || leafLocalName === localName)) {
145
- res = node;
146
- }
147
- } else if (leafName === '*' || leafName === localName) {
141
+ if (astType === TYPE_SELECTOR && nodeType === ELEMENT_NODE) {
142
+ let astName = ast.name;
143
+ let astPrefix, astNodeName, nodePrefix, nodeName;
144
+ if (/\|/.test(astName)) {
145
+ [astPrefix, astNodeName] = astName.split('|');
146
+ } else {
147
+ astPrefix = '';
148
+ astNodeName = astName;
149
+ }
150
+ if (ownerDocument?.contentType === 'text/html') {
151
+ astNodeName = astNodeName.toLowerCase();
152
+ astName = astName.toLowerCase();
153
+ }
154
+ // just in case that the namespaced content is parsed as text/html
155
+ if (/:/.test(localName)) {
156
+ [nodePrefix, nodeName] = localName.split(':');
157
+ } else {
158
+ nodePrefix = prefix || '';
159
+ nodeName = localName;
160
+ }
161
+ if (astName === '*' || astName === '*|*' || astName === nodeName ||
162
+ (astName === '|*' && !nodePrefix) ||
163
+ (astPrefix === '*' && astNodeName === nodeName) ||
164
+ (astPrefix === '' && !nodePrefix &&
165
+ (astNodeName === '*' || astNodeName === nodeName)) ||
166
+ (astPrefix === nodePrefix &&
167
+ (astNodeName === '*' || astNodeName === nodeName))) {
148
168
  res = node;
149
169
  }
150
170
  }
@@ -153,16 +173,16 @@ const matchTypeSelector = (leaf = {}, node = {}) => {
153
173
 
154
174
  /**
155
175
  * match class selector
156
- * @param {object} leaf - ast leaf
157
- * @param {object} node - element node
176
+ * @param {object} ast - AST
177
+ * @param {object} node - Element node
158
178
  * @returns {?object} - matched node
159
179
  */
160
- const matchClassSelector = (leaf = {}, node = {}) => {
161
- const { name: leafName, type: leafType } = leaf;
180
+ const matchClassSelector = (ast = {}, node = {}) => {
181
+ const { name: astName, type: astType } = ast;
162
182
  const { classList, nodeType } = node;
163
183
  let res;
164
- if (leafType === CLASS_SELECTOR && nodeType === ELEMENT_NODE &&
165
- classList.contains(leafName)) {
184
+ if (astType === CLASS_SELECTOR && nodeType === ELEMENT_NODE &&
185
+ classList.contains(astName)) {
166
186
  res = node;
167
187
  }
168
188
  return res || null;
@@ -170,16 +190,16 @@ const matchClassSelector = (leaf = {}, node = {}) => {
170
190
 
171
191
  /**
172
192
  * match ID selector
173
- * @param {object} leaf - ast leaf
174
- * @param {object} node - element node
193
+ * @param {object} ast - AST
194
+ * @param {object} node - Element node
175
195
  * @returns {?object} - matched node
176
196
  */
177
- const matchIdSelector = (leaf = {}, node = {}) => {
178
- const { name: leafName, type: leafType } = leaf;
197
+ const matchIdSelector = (ast = {}, node = {}) => {
198
+ const { name: astName, type: astType } = ast;
179
199
  const { id, nodeType } = node;
180
200
  let res;
181
- if (leafType === ID_SELECTOR && nodeType === ELEMENT_NODE &&
182
- leafName === id) {
201
+ if (astType === ID_SELECTOR && nodeType === ELEMENT_NODE &&
202
+ astName === id) {
183
203
  res = node;
184
204
  }
185
205
  return res || null;
@@ -187,32 +207,32 @@ const matchIdSelector = (leaf = {}, node = {}) => {
187
207
 
188
208
  /**
189
209
  * match attribute selector
190
- * @param {object} leaf - ast leaf
191
- * @param {object} node - element node
210
+ * @param {object} ast - AST
211
+ * @param {object} node - Element node
192
212
  * @returns {?object} - matched node
193
213
  */
194
- const matchAttributeSelector = (leaf = {}, node = {}) => {
214
+ const matchAttributeSelector = (ast = {}, node = {}) => {
195
215
  const {
196
- flags: leafFlags, matcher: leafMatcher, name: leafName, type: leafType,
197
- value: leafValue
198
- } = leaf;
216
+ flags: astFlags, matcher: astMatcher, name: astName, type: astType,
217
+ value: astValue
218
+ } = ast;
199
219
  const { attributes, nodeType } = node;
200
220
  let res;
201
- if (leafType === ATTRIBUTE_SELECTOR && nodeType === ELEMENT_NODE &&
221
+ if (astType === ATTRIBUTE_SELECTOR && nodeType === ELEMENT_NODE &&
202
222
  attributes?.length) {
203
- const { name: leafAttrName } = leafName;
204
- const caseInsensitive = !(leafFlags && /^s$/i.test(leafFlags));
223
+ const { name: astAttrName } = astName;
224
+ const caseInsensitive = !(astFlags && /^s$/i.test(astFlags));
205
225
  const attrValues = [];
206
226
  const l = attributes.length;
207
227
  // namespaced
208
- if (/\|/.test(leafAttrName)) {
209
- const [leafAttrPrefix, leafAttrLocalName] = leafAttrName.split('|');
228
+ if (/\|/.test(astAttrName)) {
229
+ const [astAttrPrefix, astAttrLocalName] = astAttrName.split('|');
210
230
  let i = 0;
211
231
  while (i < l) {
212
232
  const { name: itemName, value: itemValue } = attributes.item(i);
213
- switch (leafAttrPrefix) {
233
+ switch (astAttrPrefix) {
214
234
  case '':
215
- if (leafAttrLocalName === itemName) {
235
+ if (astAttrLocalName === itemName) {
216
236
  if (caseInsensitive) {
217
237
  attrValues.push(itemValue.toLowerCase());
218
238
  } else {
@@ -222,14 +242,14 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
222
242
  break;
223
243
  case '*':
224
244
  if (/:/.test(itemName)) {
225
- if (itemName.endsWith(`:${leafAttrLocalName}`)) {
245
+ if (itemName.endsWith(`:${astAttrLocalName}`)) {
226
246
  if (caseInsensitive) {
227
247
  attrValues.push(itemValue.toLowerCase());
228
248
  } else {
229
249
  attrValues.push(itemValue);
230
250
  }
231
251
  }
232
- } else if (leafAttrLocalName === itemName) {
252
+ } else if (astAttrLocalName === itemName) {
233
253
  if (caseInsensitive) {
234
254
  attrValues.push(itemValue.toLowerCase());
235
255
  } else {
@@ -240,8 +260,8 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
240
260
  default:
241
261
  if (/:/.test(itemName)) {
242
262
  const [itemNamePrefix, itemNameLocalName] = itemName.split(':');
243
- if (leafAttrPrefix === itemNamePrefix &&
244
- leafAttrLocalName === itemNameLocalName) {
263
+ if (astAttrPrefix === itemNamePrefix &&
264
+ astAttrLocalName === itemNameLocalName) {
245
265
  if (caseInsensitive) {
246
266
  attrValues.push(itemValue.toLowerCase());
247
267
  } else {
@@ -258,14 +278,14 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
258
278
  const { name: itemName, value: itemValue } = attributes.item(i);
259
279
  if (/:/.test(itemName)) {
260
280
  const [, itemNameLocalName] = itemName.split(':');
261
- if (leafAttrName === itemNameLocalName) {
281
+ if (astAttrName === itemNameLocalName) {
262
282
  if (caseInsensitive) {
263
283
  attrValues.push(itemValue.toLowerCase());
264
284
  } else {
265
285
  attrValues.push(itemValue);
266
286
  }
267
287
  }
268
- } else if (leafAttrName === itemName) {
288
+ } else if (astAttrName === itemName) {
269
289
  if (caseInsensitive) {
270
290
  attrValues.push(itemValue.toLowerCase());
271
291
  } else {
@@ -277,23 +297,23 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
277
297
  }
278
298
  if (attrValues.length) {
279
299
  const {
280
- name: leafAttrIdentValue, value: leafAttrStringValue
281
- } = leafValue || {};
300
+ name: astAttrIdentValue, value: astAttrStringValue
301
+ } = astValue || {};
282
302
  let attrValue;
283
- if (leafAttrIdentValue) {
303
+ if (astAttrIdentValue) {
284
304
  if (caseInsensitive) {
285
- attrValue = leafAttrIdentValue.toLowerCase();
305
+ attrValue = astAttrIdentValue.toLowerCase();
286
306
  } else {
287
- attrValue = leafAttrIdentValue;
307
+ attrValue = astAttrIdentValue;
288
308
  }
289
- } else if (leafAttrStringValue) {
309
+ } else if (astAttrStringValue) {
290
310
  if (caseInsensitive) {
291
- attrValue = leafAttrStringValue.toLowerCase();
311
+ attrValue = astAttrStringValue.toLowerCase();
292
312
  } else {
293
- attrValue = leafAttrStringValue;
313
+ attrValue = astAttrStringValue;
294
314
  }
295
315
  }
296
- switch (leafMatcher) {
316
+ switch (astMatcher) {
297
317
  case null:
298
318
  res = node;
299
319
  break;
@@ -354,7 +374,7 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
354
374
  }
355
375
  break;
356
376
  default:
357
- console.warn(`Unknown matcher ${leafMatcher}`);
377
+ console.warn(`Unknown matcher ${astMatcher}`);
358
378
  }
359
379
  }
360
380
  }
@@ -363,34 +383,34 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
363
383
 
364
384
  /**
365
385
  * match An+B
366
- * @param {string} leafName - leaf name
367
- * @param {object} leaf - ast leaf
368
- * @param {object} node - element node
386
+ * @param {string} nthName - nth pseudo class name
387
+ * @param {object} ast - AST
388
+ * @param {object} node - Element node
369
389
  * @returns {Array.<object|undefined>} - collection of matched nodes
370
390
  */
371
- const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
391
+ const matchAnPlusB = (nthName, ast = {}, node = {}) => {
372
392
  const res = new Set();
373
- if (typeof leafName === 'string') {
374
- leafName = leafName.trim();
375
- if (REG_PSEUDO_NTH.test(leafName)) {
393
+ if (typeof nthName === 'string') {
394
+ nthName = nthName.trim();
395
+ if (REG_PSEUDO_NTH.test(nthName)) {
376
396
  const {
377
397
  nth: {
378
398
  a,
379
399
  b,
380
400
  name: identName
381
401
  },
382
- selector: leafSelector,
383
- type: leafType
384
- } = leaf;
402
+ selector: astSelector,
403
+ type: astType
404
+ } = ast;
385
405
  const { nodeType } = node;
386
- if (leafType === NTH && nodeType === ELEMENT_NODE) {
406
+ if (astType === NTH && nodeType === ELEMENT_NODE) {
387
407
  /*
388
408
  // FIXME:
389
409
  // :nth-child(An+B of S)
390
- if (leafSelector) {
410
+ if (astSelector) {
391
411
  }
392
412
  */
393
- if (!leafSelector) {
413
+ if (!astSelector) {
394
414
  const optMap = new Map();
395
415
  if (identName) {
396
416
  if (identName === 'even') {
@@ -400,7 +420,7 @@ const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
400
420
  optMap.set('a', 2);
401
421
  optMap.set('b', 1);
402
422
  }
403
- if (/last/.test(leafName)) {
423
+ if (/last/.test(nthName)) {
404
424
  optMap.set('reverse', true);
405
425
  }
406
426
  } else {
@@ -414,20 +434,20 @@ const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
414
434
  } else {
415
435
  optMap.set('b', 0);
416
436
  }
417
- if (/last/.test(leafName)) {
437
+ if (/last/.test(nthName)) {
418
438
  optMap.set('reverse', true);
419
439
  }
420
440
  }
421
441
  if (optMap.size > 1) {
422
442
  const opt = Object.fromEntries(optMap);
423
- if (/^nth-(?:last-)?child$/.test(leafName)) {
443
+ if (/^nth-(?:last-)?child$/.test(nthName)) {
424
444
  const arr = collectNthChild(node, opt);
425
445
  if (arr.length) {
426
446
  for (const i of arr) {
427
447
  res.add(i);
428
448
  }
429
449
  }
430
- } else if (/^nth-(?:last-)?of-type$/.test(leafName)) {
450
+ } else if (/^nth-(?:last-)?of-type$/.test(nthName)) {
431
451
  const arr = collectNthOfType(node, opt);
432
452
  if (arr.length) {
433
453
  for (const i of arr) {
@@ -446,29 +466,29 @@ const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
446
466
  /**
447
467
  * match language pseudo class
448
468
  * @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
449
- * @param {object} leaf - ast leaf
450
- * @param {object} node - element node
469
+ * @param {object} ast - AST
470
+ * @param {object} node - Element node
451
471
  * @returns {?object} - matched node
452
472
  */
453
- const matchLanguagePseudoClass = (leaf = {}, node = {}) => {
454
- const { name: leafName, type: leafType } = leaf;
473
+ const matchLanguagePseudoClass = (ast = {}, node = {}) => {
474
+ const { name: astName, type: astType } = ast;
455
475
  const { lang, nodeType } = node;
456
476
  let res;
457
- if (leafType === IDENTIFIER && nodeType === ELEMENT_NODE) {
477
+ if (astType === IDENTIFIER && nodeType === ELEMENT_NODE) {
458
478
  // FIXME:
459
479
  /*
460
- if (leafName === '') {
480
+ if (astName === '') {
461
481
  if (!lang) {
462
482
  res = node;
463
483
  }
464
- } else if (leafName === '*') {
484
+ } else if (astName === '*') {
465
485
  }
466
486
  */
467
- if (/[A-Za-z\d-]+/.test(leafName)) {
487
+ if (/[A-Za-z\d-]+/.test(astName)) {
468
488
  const codePart = '(?:-[A-Za-z\\d]+)?';
469
489
  let reg;
470
- if (/-/.test(leafName)) {
471
- const [langMain, langSub, ...langRest] = leafName.split('-');
490
+ if (/-/.test(astName)) {
491
+ const [langMain, langSub, ...langRest] = astName.split('-');
472
492
  const extendedMain = `${langMain}${codePart}`;
473
493
  const extendedSub = `-${langSub}${codePart}`;
474
494
  let extendedRest = '';
@@ -479,7 +499,7 @@ const matchLanguagePseudoClass = (leaf = {}, node = {}) => {
479
499
  }
480
500
  reg = new RegExp(`^${extendedMain}${extendedSub}${extendedRest}$`, 'i');
481
501
  } else {
482
- reg = new RegExp(`^${leafName}${codePart}$`, 'i');
502
+ reg = new RegExp(`^${astName}${codePart}$`, 'i');
483
503
  }
484
504
  if (lang) {
485
505
  if (reg.test(lang)) {
@@ -502,55 +522,55 @@ const matchLanguagePseudoClass = (leaf = {}, node = {}) => {
502
522
 
503
523
  /**
504
524
  * match pseudo class selector
505
- * @param {object} leaf - ast leaf
506
- * @param {object} node - element node
525
+ * @param {object} ast - AST
526
+ * @param {object} node - Element node
507
527
  * @param {object} [refPoint] - reference point
508
528
  * @returns {Array.<object|undefined>} - collection of matched nodes
509
529
  */
510
530
  const matchPseudoClassSelector = (
511
- leaf = {},
531
+ ast = {},
512
532
  node = {},
513
533
  refPoint = {}
514
534
  ) => {
515
- const { children: leafChildren, name: leafName, type: leafType } = leaf;
535
+ const { children: astChildren, name: astName, type: astType } = ast;
516
536
  const { nodeType, ownerDocument } = node;
517
537
  const res = new Set();
518
- if (leafType === PSEUDO_CLASS_SELECTOR && nodeType === ELEMENT_NODE) {
519
- if (Array.isArray(leafChildren)) {
520
- const [leafChildAst] = leafChildren;
538
+ if (astType === PSEUDO_CLASS_SELECTOR && nodeType === ELEMENT_NODE) {
539
+ if (Array.isArray(astChildren)) {
540
+ const [astChildAst] = astChildren;
521
541
  // :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
522
- if (REG_PSEUDO_NTH.test(leafName)) {
523
- const arr = matchAnPlusB(leafName, leafChildAst, node);
542
+ if (REG_PSEUDO_NTH.test(astName)) {
543
+ const arr = matchAnPlusB(astName, astChildAst, node);
524
544
  if (arr.length) {
525
545
  for (const i of arr) {
526
546
  res.add(i);
527
547
  }
528
548
  }
529
549
  } else {
530
- switch (leafName) {
550
+ switch (astName) {
531
551
  case 'dir':
532
- if (leafChildAst.name === node.dir) {
552
+ if (astChildAst.name === node.dir) {
533
553
  res.add(node);
534
554
  }
535
555
  break;
536
556
  case 'lang':
537
- if (matchLanguagePseudoClass(leafChildAst, node)) {
557
+ if (matchLanguagePseudoClass(astChildAst, node)) {
538
558
  res.add(node);
539
559
  }
540
560
  break;
541
561
  case 'current':
542
562
  case 'nth-col':
543
563
  case 'nth-last-col':
544
- console.warn(`Unsupported pseudo class ${leafName}`);
564
+ console.warn(`Unsupported pseudo class ${astName}`);
545
565
  break;
546
566
  default:
547
- console.warn(`Unknown pseudo class ${leafName}`);
567
+ console.warn(`Unknown pseudo class ${astName}`);
548
568
  }
549
569
  }
550
570
  } else {
551
571
  const root = ownerDocument.documentElement;
552
572
  const docURL = new URL(ownerDocument.URL);
553
- switch (leafName) {
573
+ switch (astName) {
554
574
  case 'any-link':
555
575
  case 'link':
556
576
  // FIXME: what about namespaced href? e.g. xlink:href
@@ -576,6 +596,19 @@ const matchPseudoClassSelector = (
576
596
  res.add(node);
577
597
  }
578
598
  break;
599
+ case 'target-within':
600
+ if (docURL.hash) {
601
+ const hash = docURL.hash.replace(/^#/, '');
602
+ let current = ownerDocument.getElementById(hash);
603
+ while (current) {
604
+ if (current === node) {
605
+ res.add(node);
606
+ break;
607
+ }
608
+ current = current.parentNode;
609
+ }
610
+ }
611
+ break;
579
612
  case 'scope':
580
613
  if (refPoint?.nodeType === ELEMENT_NODE) {
581
614
  if (node === refPoint) {
@@ -590,6 +623,17 @@ const matchPseudoClassSelector = (
590
623
  res.add(node);
591
624
  }
592
625
  break;
626
+ case 'focus-within': {
627
+ let current = ownerDocument.activeElement;
628
+ while (current) {
629
+ if (current === node) {
630
+ res.add(node);
631
+ break;
632
+ }
633
+ current = current.parentNode;
634
+ }
635
+ break;
636
+ }
593
637
  case 'open':
594
638
  if (node.hasAttribute('open')) {
595
639
  res.add(node);
@@ -693,7 +737,6 @@ const matchPseudoClassSelector = (
693
737
  case 'default':
694
738
  case 'empty':
695
739
  case 'focus-visible':
696
- case 'focus-within':
697
740
  case 'fullscreen':
698
741
  case 'future':
699
742
  case 'hover':
@@ -712,15 +755,14 @@ const matchPseudoClassSelector = (
712
755
  case 'read-write':
713
756
  case 'seeking':
714
757
  case 'stalled':
715
- case 'target-within':
716
758
  case 'user-invalid':
717
759
  case 'user-valid':
718
760
  case 'valid':
719
761
  case 'volume-locked':
720
- console.warn(`Unsupported pseudo class ${leafName}`);
762
+ console.warn(`Unsupported pseudo class ${astName}`);
721
763
  break;
722
764
  default:
723
- console.warn(`Unknown pseudo class ${leafName}`);
765
+ console.warn(`Unknown pseudo class ${astName}`);
724
766
  }
725
767
  }
726
768
  }
@@ -751,7 +793,7 @@ class Matcher {
751
793
 
752
794
  /**
753
795
  * create iterator
754
- * @param {object} ast - ast
796
+ * @param {object} ast - AST
755
797
  * @param {object} root - root node
756
798
  * @returns {object} - iterator
757
799
  */
@@ -769,8 +811,8 @@ class Matcher {
769
811
 
770
812
  /**
771
813
  * parse ast and run
772
- * @param {object} ast - ast
773
- * @param {object} node - element node
814
+ * @param {object} ast - AST
815
+ * @param {object} node - Element node
774
816
  * @returns {Array.<object|undefined>} - collection of matched nodes
775
817
  */
776
818
  _parseAst(ast, node) {
@@ -791,8 +833,8 @@ class Matcher {
791
833
 
792
834
  /**
793
835
  * match adjacent leaves
794
- * @param {Array.<object>} leaves - array of ast leaves
795
- * @param {object} node - element node
836
+ * @param {Array.<object>} leaves - array of AST leaves
837
+ * @param {object} node - Element node
796
838
  * @returns {?object} - matched node
797
839
  */
798
840
  _matchAdjacentLeaves(leaves, node) {
@@ -827,8 +869,8 @@ class Matcher {
827
869
 
828
870
  /**
829
871
  * match combinator
830
- * @param {Array.<object>} leaves - array of ast leaves
831
- * @param {object} prevNode - element node
872
+ * @param {Array.<object>} leaves - array of AST leaves
873
+ * @param {object} prevNode - Element node
832
874
  * @returns {Array.<object|undefined>} - matched nodes
833
875
  */
834
876
  _matchCombinator(leaves, prevNode) {
@@ -853,10 +895,10 @@ class Matcher {
853
895
  nextNode = iterator.nextNode();
854
896
  }
855
897
  while (items.length) {
856
- const leaf = items.shift();
898
+ const ast = items.shift();
857
899
  if (nodes.size) {
858
900
  nodes.forEach(node => {
859
- const arr = this._match(leaf, node);
901
+ const arr = this._match(ast, node);
860
902
  if (!arr.length) {
861
903
  nodes.delete(node);
862
904
  }
@@ -907,8 +949,8 @@ class Matcher {
907
949
 
908
950
  /**
909
951
  * match argument leaf
910
- * @param {object} leaf - argument ast leaf
911
- * @param {object} node - element node
952
+ * @param {object} leaf - AST leaf
953
+ * @param {object} node - Element node
912
954
  * @returns {Array.<object|undefined>} - matched nodes
913
955
  */
914
956
  _matchArgumentLeaf(leaf, node) {
@@ -929,16 +971,16 @@ class Matcher {
929
971
 
930
972
  /**
931
973
  * match logical pseudo class functions - :is(), :has(), :not(), :where()
932
- * @param {object} leaf - ast leaf
933
- * @param {object} node - element node
974
+ * @param {object} branch - AST branch
975
+ * @param {object} node - Element node
934
976
  * @returns {?object} - matched node
935
977
  */
936
- _matchLogicalPseudoFunc(leaf, node) {
937
- const ast = walkAst(leaf);
978
+ _matchLogicalPseudoFunc(branch, node) {
979
+ const ast = walkAst(branch);
938
980
  let res;
939
981
  if (ast.length) {
940
- const { name: leafName } = leaf;
941
- switch (leafName) {
982
+ const { name: branchName } = branch;
983
+ switch (branchName) {
942
984
  // :has()
943
985
  case 'has': {
944
986
  let matched;
@@ -1019,7 +1061,7 @@ class Matcher {
1019
1061
  /**
1020
1062
  * match selector
1021
1063
  * @param {Array.<object>} children - selector children
1022
- * @param {object} node - element node
1064
+ * @param {object} node - Element node
1023
1065
  * @returns {Array.<object|undefined>} - collection of matched nodes
1024
1066
  */
1025
1067
  _matchSelector(children, node) {
@@ -1120,9 +1162,9 @@ class Matcher {
1120
1162
  }
1121
1163
 
1122
1164
  /**
1123
- * match ast and node
1124
- * @param {object} [ast] - ast tree
1125
- * @param {object} [node] - element node
1165
+ * match AST and node
1166
+ * @param {object} ast - AST
1167
+ * @param {object} node - Element node
1126
1168
  * @returns {Array.<object|undefined>} - collection of matched nodes
1127
1169
  */
1128
1170
  _match(ast = this.#ast, node = this.#node) {
package/src/js/parser.js CHANGED
@@ -22,19 +22,19 @@ const parseSelector = selector => {
22
22
  /**
23
23
  * walk AST
24
24
  * @param {object} ast - AST
25
- * @returns {Array.<object|undefined>} - array of selectors
25
+ * @returns {Array.<object|undefined>} - collection of AST branches
26
26
  */
27
27
  const walkAst = (ast = {}) => {
28
28
  const selectors = new Set();
29
29
  const opt = {
30
- enter: leaf => {
31
- if (leaf.type === SELECTOR) {
32
- selectors.add(leaf.children);
30
+ enter: branch => {
31
+ if (branch.type === SELECTOR) {
32
+ selectors.add(branch.children);
33
33
  }
34
34
  },
35
- leave: leaf => {
35
+ leave: branch => {
36
36
  let skip;
37
- if (leaf.type === SELECTOR) {
37
+ if (branch.type === SELECTOR) {
38
38
  skip = walkAst.skip;
39
39
  }
40
40
  return skip;
@@ -1,41 +1,13 @@
1
1
  export const AN_PLUS_B: string;
2
- export const AT_RULE: string;
3
- export const AT_RULE_PRELUDE: string;
4
2
  export const ATTRIBUTE_SELECTOR: string;
5
- export const BLOCK: string;
6
- export const BRACKETS: string;
7
- export const CDC: string;
8
- export const CDO: string;
9
3
  export const CLASS_SELECTOR: string;
10
4
  export const COMBINATOR: string;
11
- export const COMMENT: string;
12
- export const DECLARATION: string;
13
- export const DECLARATION_LIST: string;
14
- export const DIMENSION: string;
15
- export const FUNCTION: string;
16
- export const HASH: string;
17
5
  export const ID_SELECTOR: string;
18
6
  export const IDENTIFIER: string;
19
- export const MEDIA_FEATURE: string;
20
- export const MEDIA_QUERY: string;
21
- export const MEDIA_QUERY_LIST: string;
22
- export const NESTING_SELECTOR: string;
23
7
  export const NTH: string;
24
- export const NUMBER: string;
25
- export const OPERATOR: string;
26
- export const PARENTHESES: string;
27
- export const PERCENTAGE: string;
28
8
  export const PSEUDO_CLASS_SELECTOR: string;
29
- export const PSEUDO_ELEMENT_SELECTOR: string;
30
- export const RATIO: string;
31
9
  export const RAW: string;
32
- export const RULE: string;
33
10
  export const SELECTOR: string;
34
11
  export const SELECTOR_LIST: string;
35
12
  export const STRING: string;
36
- export const STYLE_SHEET: string;
37
13
  export const TYPE_SELECTOR: string;
38
- export const UNICODE_RANGE: string;
39
- export const URL: string;
40
- export const VALUE: string;
41
- export const WHITESPACE: string;
@@ -5,7 +5,7 @@ export class Matcher {
5
5
  _matchAdjacentLeaves(leaves: Array<object>, node: object): object | null;
6
6
  _matchCombinator(leaves: Array<object>, prevNode: object): Array<object | undefined>;
7
7
  _matchArgumentLeaf(leaf: object, node: object): Array<object | undefined>;
8
- _matchLogicalPseudoFunc(leaf: object, node: object): object | null;
8
+ _matchLogicalPseudoFunc(branch: object, node: object): object | null;
9
9
  _matchSelector(children: Array<object>, node: object): Array<object | undefined>;
10
10
  _match(ast?: object, node?: object): Array<object | undefined>;
11
11
  matches(): boolean;
@@ -24,10 +24,10 @@ export function collectNthOfType(node?: object, opt?: {
24
24
  b: number;
25
25
  reverse?: boolean;
26
26
  }): Array<object | undefined>;
27
- export function matchAnPlusB(leafName: string, leaf?: object, node?: object): Array<object | undefined>;
28
- export function matchAttributeSelector(leaf?: object, node?: object): object | null;
29
- export function matchClassSelector(leaf?: object, node?: object): object | null;
30
- export function matchIdSelector(leaf?: object, node?: object): object | null;
31
- export function matchLanguagePseudoClass(leaf?: object, node?: object): object | null;
32
- export function matchPseudoClassSelector(leaf?: object, node?: object, refPoint?: object): Array<object | undefined>;
33
- export function matchTypeSelector(leaf?: object, node?: object): object | null;
27
+ export function matchAnPlusB(nthName: string, ast?: object, node?: object): Array<object | undefined>;
28
+ export function matchAttributeSelector(ast?: object, node?: object): object | null;
29
+ export function matchClassSelector(ast?: object, node?: object): object | null;
30
+ export function matchIdSelector(ast?: object, node?: object): object | null;
31
+ export function matchLanguagePseudoClass(ast?: object, node?: object): object | null;
32
+ export function matchPseudoClassSelector(ast?: object, node?: object, refPoint?: object): Array<object | undefined>;
33
+ export function matchTypeSelector(ast?: object, node?: object): object | null;