@asamuzakjp/dom-selector 0.8.0 → 0.9.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
@@ -3,6 +3,7 @@
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
5
  [![npm (scoped)](https://img.shields.io/npm/v/@asamuzakjp/dom-selector)](https://www.npmjs.com/package/@asamuzakjp/dom-selector)
6
+
6
7
  <!--
7
8
  [![release](https://img.shields.io/github/v/release/asamuzaK/domSelector)](https://github.com/asamuzaK/domSelector/releases)
8
9
  -->
@@ -10,14 +11,12 @@
10
11
  Retrieve DOM node from the given CSS selector.
11
12
  **Experimental**
12
13
 
13
-
14
14
  ## Install
15
15
 
16
16
  ```console
17
17
  npm i @asamuzakjp/dom-selector
18
18
  ```
19
19
 
20
-
21
20
  ## Usage
22
21
 
23
22
  ```javascript
@@ -28,65 +27,65 @@ const {
28
27
 
29
28
  <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
30
29
 
31
- #### Table of Contents
32
-
33
- - [matches][1]
34
- - [Parameters][2]
35
- - [closest][3]
36
- - [Parameters][4]
37
- - [querySelector][5]
38
- - [Parameters][6]
39
- - [querySelectorAll][7]
40
- - [Parameters][8]
41
-
42
-
43
- ### matches(selector, node)
30
+ ### matches(selector, node, opt)
44
31
 
45
- Implementation of [Element.matches()][62].
32
+ matches - [Element.matches()][64]
46
33
 
47
34
  #### Parameters
48
35
 
49
- - `selector` **[string][56]** CSS selector
50
- - `node` **[object][57]** Referenced Element node
36
+ - `selector` **[string][59]** CSS selector
37
+ - `node` **[object][60]** Element node
38
+ - `opt` **[object][60]?** options
39
+ - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
40
+ - `opt.jsdom` **[boolean][61]?** is jsdom
51
41
 
52
- Returns **[boolean][58]** Result
42
+ Returns **[boolean][61]** result
53
43
 
54
44
 
55
- ### closest(selector, node)
45
+ ### closest(selector, node, opt)
56
46
 
57
- Implementation of [Element.closest()][63].
47
+ closest - [Element.closest()][65]
58
48
 
59
49
  #### Parameters
60
50
 
61
- - `selector` **[string][56]** CSS selector
62
- - `node` **[object][57]** Referenced Element node
51
+ - `selector` **[string][59]** CSS selector
52
+ - `node` **[object][60]** Element node
53
+ - `opt` **[object][60]?** options
54
+ - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
55
+ - `opt.jsdom` **[boolean][61]?** is jsdom
63
56
 
64
- Returns **[object][57]?** Matched node
57
+ Returns **[object][60]?** matched node
65
58
 
66
59
 
67
- ### querySelector(selector, refPoint)
60
+ ### querySelector(selector, refPoint, opt)
68
61
 
69
- Implementation of [Document.querySelector()][64], [Element.querySelector()][65].
62
+ querySelector - [Document.querySelector()][66], [DocumentFragment.querySelector()][67], [Element.querySelector()][68]
70
63
 
71
64
  #### Parameters
72
65
 
73
- - `selector` **[string][56]** CSS selector
74
- - `refPoint` **[object][57]** Reference point. Document or Element node
66
+ - `selector` **[string][59]** CSS selector
67
+ - `refPoint` **[object][60]** Document, DocumentFragment or Element node
68
+ - `opt` **[object][60]?** options
69
+ - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
70
+ - `opt.jsdom` **[boolean][61]?** is jsdom
75
71
 
76
- Returns **[object][57]?** Matched node
72
+ Returns **[object][60]?** matched node
77
73
 
78
74
 
79
- ### querySelectorAll(selector, refPoint)
75
+ ### querySelectorAll(selector, refPoint, opt)
80
76
 
81
- Implementation of [Document.querySelectorAll()][66], [Element.querySelectorAll()][67].
82
- **NOTE**: returns [Array][59], not [NodeList][61].
77
+ querySelectorAll - [Document.querySelectorAll()][69], [Document.querySelectorAll()][70], [Element.querySelectorAll()][71]
78
+ **NOTE**: returns Array, not NodeList
83
79
 
84
80
  #### Parameters
85
81
 
86
- - `selector` **[string][56]** CSS selector
87
- - `refPoint` **[object][57]** Reference point. Document or Element node
82
+ - `selector` **[string][59]** CSS selector
83
+ - `refPoint` **[object][60]** Document, DocumentFragment or Element node
84
+ - `opt` **[object][60]?** options
85
+ - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
86
+ - `opt.jsdom` **[boolean][61]?** is jsdom
88
87
 
89
- Returns **[Array][59]&lt;([object][57] \| [undefined][60])>** Array of matched nodes
88
+ Returns **[Array][62]&lt;([object][60] \| [undefined][63])>** array of matched nodes
90
89
 
91
90
 
92
91
  ## Acknowledgments
@@ -105,15 +104,16 @@ The following resources have been of great help in the development of the DOM Se
105
104
  [6]: #parameters-2
106
105
  [7]: #queryselectorall
107
106
  [8]: #parameters-3
108
- [56]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
109
- [57]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
110
- [58]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
111
- [59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
112
- [60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
113
- [61]: https://developer.mozilla.org/docs/Web/API/NodeList
114
- [62]: https://developer.mozilla.org/docs/Web/API/Element/matches
115
- [63]: https://developer.mozilla.org/docs/Web/API/Element/closest
116
- [64]: https://developer.mozilla.org/docs/Web/API/Document/querySelector
117
- [65]: https://developer.mozilla.org/docs/Web/API/Element/querySelector
118
- [66]: https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll
119
- [67]: https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll
107
+ [59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
108
+ [60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
109
+ [61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
110
+ [62]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
111
+ [63]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
112
+ [64]: https://developer.mozilla.org/docs/Web/API/Element/matches
113
+ [65]: https://developer.mozilla.org/docs/Web/API/Element/closest
114
+ [66]: https://developer.mozilla.org/docs/Web/API/Document/querySelector
115
+ [67]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelector
116
+ [68]: https://developer.mozilla.org/docs/Web/API/Element/querySelector
117
+ [69]: https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll
118
+ [70]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelectorAll
119
+ [71]: https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll
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.8.0"
44
+ "version": "0.9.2"
45
45
  }
package/src/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * @copyright asamuzaK (Kazz)
5
5
  * @see {@link https://github.com/asamuzaK/domSelector/blob/main/LICENSE}
6
6
  */
7
+ 'use strict';
7
8
 
8
9
  /* import */
9
10
  const { Matcher } = require('./js/matcher.js');
@@ -12,10 +13,13 @@ const { Matcher } = require('./js/matcher.js');
12
13
  * matches - Element.matches()
13
14
  * @param {string} selector - CSS selector
14
15
  * @param {object} node - Element node
16
+ * @param {object} [opt] - options
17
+ * @param {object} [opt.globalObject] - global object
18
+ * @param {boolean} [opt.jsdom] - is jsdom
15
19
  * @returns {boolean} - result
16
20
  */
17
- const matches = (selector, node) => {
18
- const matcher = new Matcher(selector, node);
21
+ const matches = (selector, node, opt) => {
22
+ const matcher = new Matcher(selector, node, opt);
19
23
  return matcher.matches();
20
24
  };
21
25
 
@@ -23,10 +27,13 @@ const matches = (selector, node) => {
23
27
  * closest - Element.closest()
24
28
  * @param {string} selector - CSS selector
25
29
  * @param {object} node - Element node
30
+ * @param {object} [opt] - options
31
+ * @param {object} [opt.globalObject] - global object
32
+ * @param {boolean} [opt.jsdom] - is jsdom
26
33
  * @returns {?object} - matched node
27
34
  */
28
- const closest = (selector, node) => {
29
- const matcher = new Matcher(selector, node);
35
+ const closest = (selector, node, opt) => {
36
+ const matcher = new Matcher(selector, node, opt);
30
37
  return matcher.closest();
31
38
  };
32
39
 
@@ -34,10 +41,13 @@ const closest = (selector, node) => {
34
41
  * querySelector - Document.querySelector(), Element.querySelector()
35
42
  * @param {string} selector - CSS selector
36
43
  * @param {object} refPoint - Document or Element node
44
+ * @param {object} [opt] - options
45
+ * @param {object} [opt.globalObject] - global object
46
+ * @param {boolean} [opt.jsdom] - is jsdom
37
47
  * @returns {?object} - matched node
38
48
  */
39
- const querySelector = (selector, refPoint) => {
40
- const matcher = new Matcher(selector, refPoint);
49
+ const querySelector = (selector, refPoint, opt) => {
50
+ const matcher = new Matcher(selector, refPoint, opt);
41
51
  return matcher.querySelector();
42
52
  };
43
53
 
@@ -46,10 +56,13 @@ const querySelector = (selector, refPoint) => {
46
56
  * NOTE: returns Array, not NodeList
47
57
  * @param {string} selector - CSS selector
48
58
  * @param {object} refPoint - Document or Element node
59
+ * @param {object} [opt] - options
60
+ * @param {object} [opt.globalObject] - global object
61
+ * @param {boolean} [opt.jsdom] - is jsdom
49
62
  * @returns {Array.<object|undefined>} - array of matched nodes
50
63
  */
51
- const querySelectorAll = (selector, refPoint) => {
52
- const matcher = new Matcher(selector, refPoint);
64
+ const querySelectorAll = (selector, refPoint, opt) => {
65
+ const matcher = new Matcher(selector, refPoint, opt);
53
66
  return matcher.querySelectorAll();
54
67
  };
55
68
 
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * constant.js
3
3
  */
4
+ 'use strict';
4
5
 
5
6
  module.exports = {
6
7
  AN_PLUS_B: 'AnPlusB',
@@ -0,0 +1,13 @@
1
+ /**
2
+ * domexception.js
3
+ */
4
+ 'use strict';
5
+
6
+ // NOTE: Node.js has DOMException global since v17.0.0
7
+ if (!globalThis.DOMException) {
8
+ /* import */
9
+ const DOMException = require('domexception');
10
+ globalThis.DOMException = DOMException;
11
+ }
12
+
13
+ module.exports = globalThis.DOMException;
package/src/js/matcher.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * matcher.js
3
3
  */
4
+ 'use strict';
4
5
 
5
6
  /* import */
6
- const DOMException = require('domexception');
7
+ const _DOMException = require('domexception/webidl2js-wrapper');
8
+ const DOMException = require('./domexception.js');
7
9
  const { generateCSS, parseSelector, walkAST } = require('./parser.js');
8
10
 
9
11
  /* constants */
@@ -345,8 +347,12 @@ const matchAttributeSelector = (ast = {}, node = {}) => {
345
347
  let res;
346
348
  if (astType === ATTRIBUTE_SELECTOR && nodeType === ELEMENT_NODE &&
347
349
  attributes?.length) {
350
+ if (typeof astFlags === 'string' && !/^[is]$/i.test(astFlags)) {
351
+ throw new DOMException('invalid attribute selector', 'SyntaxError');
352
+ }
348
353
  const { name: astAttrName } = astName;
349
- const caseInsensitive = !(astFlags && /^s$/i.test(astFlags));
354
+ const caseInsensitive =
355
+ !(typeof astFlags === 'string' && /^s$/i.test(astFlags));
350
356
  const attrValues = [];
351
357
  const l = attributes.length;
352
358
  // namespaced
@@ -883,6 +889,8 @@ class Matcher {
883
889
  /* private fields */
884
890
  #ast;
885
891
  #document;
892
+ #global;
893
+ #jsdom;
886
894
  #node;
887
895
  #selector;
888
896
 
@@ -890,14 +898,32 @@ class Matcher {
890
898
  * construct
891
899
  * @param {string} selector - CSS selector
892
900
  * @param {object} refPoint - reference point
901
+ * @param {object} [opt] - options
902
+ * @param {object} [opt.globalObject] - global object
903
+ * @param {boolean} [opt.jsdom] - is jsdom
893
904
  */
894
- constructor(selector, refPoint) {
905
+ constructor(selector, refPoint, opt = {}) {
906
+ const { globalObject, jsdom } = opt;
895
907
  this.#ast = parseSelector(selector);
896
908
  this.#document = refPoint?.ownerDocument ?? refPoint;
909
+ this.#global = globalObject || globalThis;
910
+ this.#jsdom = !!jsdom;
897
911
  this.#node = refPoint;
898
912
  this.#selector = selector;
899
913
  }
900
914
 
915
+ /**
916
+ * create DOMException
917
+ * @param {string} msg - message
918
+ * @param {string} name - name
919
+ * @throws
920
+ */
921
+ _createDOMException(msg, name) {
922
+ if (this.#jsdom) {
923
+ throw _DOMException.create(this.#global, [msg, name]);
924
+ }
925
+ }
926
+
901
927
  /**
902
928
  * create iterator
903
929
  * @param {object} ast - AST
@@ -1342,8 +1368,17 @@ class Matcher {
1342
1368
  * @returns {boolean} - matched node
1343
1369
  */
1344
1370
  matches() {
1345
- const arr = this._match(this.#ast, this.#document);
1346
- const res = arr.length && arr.includes(this.#node);
1371
+ let res;
1372
+ try {
1373
+ const arr = this._match(this.#ast, this.#document);
1374
+ res = arr.length && arr.includes(this.#node);
1375
+ } catch (e) {
1376
+ if (e instanceof DOMException && this.#jsdom) {
1377
+ res = this._createDOMException(e.message, e.name);
1378
+ } else {
1379
+ throw e;
1380
+ }
1381
+ }
1347
1382
  return !!res;
1348
1383
  }
1349
1384
 
@@ -1352,15 +1387,23 @@ class Matcher {
1352
1387
  * @returns {?object} - matched node
1353
1388
  */
1354
1389
  closest() {
1355
- const arr = this._match(this.#ast, this.#document);
1356
- let node = this.#node;
1357
1390
  let res;
1358
- while (node) {
1359
- if (arr.includes(node)) {
1360
- res = node;
1361
- break;
1391
+ try {
1392
+ const arr = this._match(this.#ast, this.#document);
1393
+ let node = this.#node;
1394
+ while (node) {
1395
+ if (arr.includes(node)) {
1396
+ res = node;
1397
+ break;
1398
+ }
1399
+ node = node.parentNode;
1400
+ }
1401
+ } catch (e) {
1402
+ if (e instanceof DOMException && this.#jsdom) {
1403
+ res = this._createDOMException(e.message, e.name);
1404
+ } else {
1405
+ throw e;
1362
1406
  }
1363
- node = node.parentNode;
1364
1407
  }
1365
1408
  return res || null;
1366
1409
  }
@@ -1370,14 +1413,23 @@ class Matcher {
1370
1413
  * @returns {?object} - matched node
1371
1414
  */
1372
1415
  querySelector() {
1373
- const arr = this._match(this.#ast, this.#node);
1374
- if (arr.length) {
1375
- const i = arr.findIndex(node => node === this.#node);
1376
- if (i >= 0) {
1377
- arr.splice(i, 1);
1416
+ let res;
1417
+ try {
1418
+ const arr = this._match(this.#ast, this.#node);
1419
+ if (arr.length) {
1420
+ const i = arr.findIndex(node => node === this.#node);
1421
+ if (i >= 0) {
1422
+ arr.splice(i, 1);
1423
+ }
1424
+ }
1425
+ [res] = arr;
1426
+ } catch (e) {
1427
+ if (e instanceof DOMException && this.#jsdom) {
1428
+ res = this._createDOMException(e.message, e.name);
1429
+ } else {
1430
+ throw e;
1378
1431
  }
1379
1432
  }
1380
- const [res] = arr;
1381
1433
  return res || null;
1382
1434
  }
1383
1435
 
@@ -1387,14 +1439,25 @@ class Matcher {
1387
1439
  * @returns {Array.<object|undefined>} - collection of matched nodes
1388
1440
  */
1389
1441
  querySelectorAll() {
1390
- const arr = this._match(this.#ast, this.#node);
1391
- if (arr.length) {
1392
- const i = arr.findIndex(node => node === this.#node);
1393
- if (i >= 0) {
1394
- arr.splice(i, 1);
1442
+ const res = [];
1443
+ try {
1444
+ const arr = this._match(this.#ast, this.#node);
1445
+ if (arr.length) {
1446
+ const i = arr.findIndex(node => node === this.#node);
1447
+ if (i >= 0) {
1448
+ arr.splice(i, 1);
1449
+ }
1450
+ }
1451
+ const a = new Set(arr);
1452
+ res.push(...a);
1453
+ } catch (e) {
1454
+ if (e instanceof DOMException && this.#jsdom) {
1455
+ res.push(this._createDOMException(e.message, e.name));
1456
+ } else {
1457
+ throw e;
1395
1458
  }
1396
1459
  }
1397
- return [...new Set(arr)];
1460
+ return res;
1398
1461
  }
1399
1462
  };
1400
1463
 
package/src/js/parser.js CHANGED
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * parser.js
3
3
  */
4
+ 'use strict';
4
5
 
5
- /* api */
6
- const DOMException = require('domexception');
6
+ /* import */
7
7
  const { generate, parse, toPlainObject, walk } = require('css-tree');
8
+ const DOMException = require('./domexception.js');
9
+
10
+ /* constants */
8
11
  const { SELECTOR } = require('./constant.js');
12
+ const TYPE_FROM = 8;
13
+ const TYPE_TO = -1;
9
14
 
10
15
  /**
11
16
  * create AST from CSS selector
@@ -13,10 +18,13 @@ const { SELECTOR } = require('./constant.js');
13
18
  * @returns {object} - AST
14
19
  */
15
20
  const parseSelector = selector => {
21
+ if (selector === undefined || selector === null) {
22
+ selector = Object.prototype.toString.call(selector)
23
+ .slice(TYPE_FROM, TYPE_TO).toLowerCase();
24
+ }
16
25
  // invalid selectors
17
26
  if (typeof selector !== 'string' || selector === '' ||
18
- selector.startsWith('>') || selector.endsWith(',') ||
19
- selector.includes('= ')) {
27
+ /^\s*>/.test(selector) || /,\s*$/.test(selector)) {
20
28
  throw new DOMException(`invalid selector ${selector}`, 'SyntaxError');
21
29
  }
22
30
  let res;
package/types/index.d.ts CHANGED
@@ -1,4 +1,16 @@
1
- export function closest(selector: string, node: object): object | null;
2
- export function matches(selector: string, node: object): boolean;
3
- export function querySelector(selector: string, refPoint: object): object | null;
4
- export function querySelectorAll(selector: string, refPoint: object): Array<object | undefined>;
1
+ export function closest(selector: string, node: object, opt?: {
2
+ globalObject?: object;
3
+ jsdom?: boolean;
4
+ }): object | null;
5
+ export function matches(selector: string, node: object, opt?: {
6
+ globalObject?: object;
7
+ jsdom?: boolean;
8
+ }): boolean;
9
+ export function querySelector(selector: string, refPoint: object, opt?: {
10
+ globalObject?: object;
11
+ jsdom?: boolean;
12
+ }): object | null;
13
+ export function querySelectorAll(selector: string, refPoint: object, opt?: {
14
+ globalObject?: object;
15
+ jsdom?: boolean;
16
+ }): Array<object | undefined>;
@@ -0,0 +1,30 @@
1
+ declare const _exports: {
2
+ new (message?: string, name?: string): DOMException;
3
+ prototype: DOMException;
4
+ readonly INDEX_SIZE_ERR: 1;
5
+ readonly DOMSTRING_SIZE_ERR: 2;
6
+ readonly HIERARCHY_REQUEST_ERR: 3;
7
+ readonly WRONG_DOCUMENT_ERR: 4;
8
+ readonly INVALID_CHARACTER_ERR: 5;
9
+ readonly NO_DATA_ALLOWED_ERR: 6;
10
+ readonly NO_MODIFICATION_ALLOWED_ERR: 7;
11
+ readonly NOT_FOUND_ERR: 8;
12
+ readonly NOT_SUPPORTED_ERR: 9;
13
+ readonly INUSE_ATTRIBUTE_ERR: 10;
14
+ readonly INVALID_STATE_ERR: 11;
15
+ readonly SYNTAX_ERR: 12;
16
+ readonly INVALID_MODIFICATION_ERR: 13;
17
+ readonly NAMESPACE_ERR: 14;
18
+ readonly INVALID_ACCESS_ERR: 15;
19
+ readonly VALIDATION_ERR: 16;
20
+ readonly TYPE_MISMATCH_ERR: 17;
21
+ readonly SECURITY_ERR: 18;
22
+ readonly NETWORK_ERR: 19;
23
+ readonly ABORT_ERR: 20;
24
+ readonly URL_MISMATCH_ERR: 21;
25
+ readonly QUOTA_EXCEEDED_ERR: 22;
26
+ readonly TIMEOUT_ERR: 23;
27
+ readonly INVALID_NODE_TYPE_ERR: 24;
28
+ readonly DATA_CLONE_ERR: 25;
29
+ };
30
+ export = _exports;
@@ -1,5 +1,9 @@
1
1
  export class Matcher {
2
- constructor(selector: string, refPoint: object);
2
+ constructor(selector: string, refPoint: object, opt?: {
3
+ globalObject?: object;
4
+ jsdom?: boolean;
5
+ });
6
+ _createDOMException(msg: string, name: string): void;
3
7
  _createIterator(ast?: object, root?: object): object;
4
8
  _parseAST(ast: object, node: object): Array<object | undefined>;
5
9
  _matchAdjacentLeaves(leaves: Array<object>, node: object): object | null;