diff --git a/package.json b/package.json index 842508e..460c5bd 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "types": "types/index.d.ts", "dependencies": { - "@asamuzakjp/nwsapi": "^2.2.24", + "@asamuzakjp/nwsapi": "^2.2.27", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1" diff --git a/src/index.js b/src/index.js index 8a0be8b..b9d8601 100644 --- a/src/index.js +++ b/src/index.js @@ -54,7 +54,8 @@ export class DOMSelector { this.#finder.onError(e, opt); } const document = node.ownerDocument; - if (document === this.#document && document.contentType === 'text/html') { + if (document === this.#document && document.contentType === 'text/html' && + node.parentNode) { const filterOpt = { complex: REG_COMPLEX.test(selector), compound: false, @@ -98,7 +99,8 @@ export class DOMSelector { this.#finder.onError(e, opt); } const document = node.ownerDocument; - if (document === this.#document && document.contentType === 'text/html') { + if (document === this.#document && document.contentType === 'text/html' && + node.parentNode) { const filterOpt = { complex: REG_COMPLEX.test(selector), compound: false, diff --git a/src/js/constant.js b/src/js/constant.js index d37155a..63e8ee7 100644 --- a/src/js/constant.js +++ b/src/js/constant.js @@ -65,29 +65,33 @@ export const N_TH = `nth-(?:last-)?(?:child|of-type)\\(\\s*(?:even|odd|${ANB})\\s*\\)`; // SUB_TYPE: attr, id, class, pseudo-class, note that [foo|=bar] is excluded export const SUB_TYPE = '\\[[^|\\]]+\\]|[#.:][\\w-]+'; +export const SUB_TYPE_WO_PSEUDO = '\\[[^|\\]]+\\]|[#.][\\w-]+'; // TAG_TYPE: *, tag export const TAG_ID_CLASS = '(?:[A-Za-z][\\w-]*|[#.][\\w-]+)'; export const TAG_TYPE = '\\*|[A-Za-z][\\w-]*'; export const TAG_TYPE_I = '\\*|[A-Z][\\w-]*'; export const COMPOUND = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE})+)`; +export const COMPOUND_WO_PSEUDO = + `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE_WO_PSEUDO})+)`; export const COMBO = '\\s?[\\s>~+]\\s?'; export const COMPLEX = `${COMPOUND}(?:${COMBO}${COMPOUND})*`; export const DESCEND = '\\s?[\\s>]\\s?'; -export const NESTED_LOGICAL_A = +export const NESTED_LOGIC_A = `:is\\(\\s*${COMPOUND}(?:\\s*,\\s*${COMPOUND})*\\s*\\)`; -export const NESTED_LOGICAL_B = +export const NESTED_LOGIC_B = `:is\\(\\s*${COMPLEX}(?:\\s*,\\s*${COMPLEX})*\\s*\\)`; export const COMPOUND_A = - `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${NESTED_LOGICAL_A})+)`; + `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${NESTED_LOGIC_A})+)`; export const COMPOUND_B = - `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${NESTED_LOGICAL_B})+)`; + `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${NESTED_LOGIC_B})+)`; export const COMPOUND_I = `(?:${TAG_TYPE_I}|(?:${TAG_TYPE_I})?(?:${SUB_TYPE})+)`; export const COMPLEX_L = `${COMPOUND_B}(?:${COMBO}${COMPOUND_B})*`; -export const LOGICAL_COMPLEX = +export const LOGIC_COMPLEX = `(?:is|not)\\(\\s*${COMPLEX_L}(?:\\s*,\\s*${COMPLEX_L})*\\s*\\)`; -export const LOGICAL_COMPOUND = +export const LOGIC_COMPOUND = `(?:is|not)\\(\\s*${COMPOUND_A}(?:\\s*,\\s*${COMPOUND_A})*\\s*\\)`; +export const HAS_COMPOUND = `has\\([\\s>~+]?\\s*${COMPOUND_WO_PSEUDO}\\s*\\)`; /* array */ export const KEY_FORM_FOCUS = diff --git a/src/js/utility.js b/src/js/utility.js index 3841f30..cb2080d 100644 --- a/src/js/utility.js +++ b/src/js/utility.js @@ -10,14 +10,16 @@ import isCustomElementName from 'is-potential-custom-element-name'; /* constants */ import { DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, DOCUMENT_POSITION_CONTAINS, - DOCUMENT_POSITION_PRECEDING, ELEMENT_NODE, KEY_INPUT_BUTTON, KEY_INPUT_EDIT, - KEY_INPUT_TEXT, LOGICAL_COMPLEX, LOGICAL_COMPOUND, N_TH, PSEUDO_CLASS, - TEXT_NODE, TYPE_FROM, TYPE_TO + DOCUMENT_POSITION_PRECEDING, ELEMENT_NODE, HAS_COMPOUND, KEY_INPUT_BUTTON, + KEY_INPUT_EDIT, KEY_INPUT_TEXT, LOGIC_COMPLEX, LOGIC_COMPOUND, N_TH, + PSEUDO_CLASS, TARGET_LINEAL, TARGET_SELF, TEXT_NODE, TYPE_FROM, TYPE_TO } from './constant.js'; -const REG_LOGICAL_COMPLEX = - new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH}|${LOGICAL_COMPLEX})`); -const REG_LOGICAL_COMPOUND = - new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH}|${LOGICAL_COMPOUND})`); +const REG_LOGIC_COMPLEX = + new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPLEX})`); +const REG_LOGIC_COMPOUND = + new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPOUND})`); +const REG_LOGIC_HAS_COMPOUND = + new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPOUND}|${HAS_COMPOUND})`); const REG_WO_LOGICAL = new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH})`); /** @@ -671,7 +673,7 @@ export const filterSelector = (selector, opt = {}) => { if (!selector || typeof selector !== 'string') { return false; } - const { complex, compound, descend, simple } = opt; + const { complex, compound, descend, simple, target } = opt; // exclude simple selector and compound selector if (simple || compound) { return false; @@ -695,11 +697,14 @@ export const filterSelector = (selector, opt = {}) => { if (selector.includes(':')) { if (descend) { return false; + } else if ((target === TARGET_SELF || target === TARGET_LINEAL) && + /:has\(/.test(selector)) { + return !REG_LOGIC_HAS_COMPOUND.test(selector); } else if (/:(?:is|not)\(/.test(selector)) { if (complex) { - return !REG_LOGICAL_COMPLEX.test(selector); + return !REG_LOGIC_COMPLEX.test(selector); } else { - return !REG_LOGICAL_COMPOUND.test(selector); + return !REG_LOGIC_COMPOUND.test(selector); } } else { return !REG_WO_LOGICAL.test(selector); diff --git a/test/utility.test.js b/test/utility.test.js index 59f4389..5e2764d 100644 --- a/test/utility.test.js +++ b/test/utility.test.js @@ -9,7 +9,9 @@ import { afterEach, beforeEach, describe, it } from 'mocha'; /* test */ import * as util from '../src/js/utility.js'; -import { WALKER_FILTER } from '../src/js/constant.js'; +import { + TARGET_SELF, TARGET_LINEAL, WALKER_FILTER +} from '../src/js/constant.js'; describe('utility functions', () => { const domStr = ` @@ -2336,5 +2338,59 @@ describe('utility functions', () => { const res = func(':nth-child()'); assert.strictEqual(res, false, 'result'); }); + + it('should get false', () => { + const res = func(':has(.foo)'); + assert.strictEqual(res, false, 'result'); + }); + + it('should get true', () => { + const res = func(':has(.foo)', { + target: TARGET_SELF + }); + assert.strictEqual(res, true, 'result'); + }); + + it('should get false', () => { + const res = func(':has(:checked)', { + target: TARGET_SELF + }); + assert.strictEqual(res, false, 'result'); + }); + + it('should get true', () => { + const res = func(':has(>.foo)', { + target: TARGET_SELF + }); + assert.strictEqual(res, true, 'result'); + }); + + it('should get false', () => { + const res = func(':has(.foo .bar)', { + target: TARGET_SELF + }); + assert.strictEqual(res, false, 'result'); + }); + + it('should get true', () => { + const res = func(':has(.foo)', { + target: TARGET_LINEAL + }); + assert.strictEqual(res, true, 'result'); + }); + + it('should get true', () => { + const res = func(':has(>.foo)', { + target: TARGET_LINEAL + }); + assert.strictEqual(res, true, 'result'); + }); + + it('should get false', () => { + const res = func(':has(.foo .bar)', { + target: TARGET_LINEAL + }); + assert.strictEqual(res, false, 'result'); + }); }); });