diff options
Diffstat (limited to 'vanilla/node_modules/@asamuzakjp/dom-selector/src/js/parser.js')
| -rw-r--r-- | vanilla/node_modules/@asamuzakjp/dom-selector/src/js/parser.js | 431 |
1 files changed, 0 insertions, 431 deletions
diff --git a/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/parser.js b/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/parser.js deleted file mode 100644 index bf06d9f..0000000 --- a/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/parser.js +++ /dev/null @@ -1,431 +0,0 @@ -/** - * parser.js - */ - -/* import */ -import * as cssTree from 'css-tree'; -import { getType } from './utility.js'; - -/* constants */ -import { - ATTR_SELECTOR, - BIT_01, - BIT_02, - BIT_04, - BIT_08, - BIT_16, - BIT_32, - BIT_FFFF, - CLASS_SELECTOR, - DUO, - HEX, - ID_SELECTOR, - KEYS_LOGICAL, - NTH, - PS_CLASS_SELECTOR, - PS_ELEMENT_SELECTOR, - SELECTOR, - SYNTAX_ERR, - TYPE_SELECTOR -} from './constant.js'; -const AST_SORT_ORDER = new Map([ - [PS_ELEMENT_SELECTOR, BIT_01], - [ID_SELECTOR, BIT_02], - [CLASS_SELECTOR, BIT_04], - [TYPE_SELECTOR, BIT_08], - [ATTR_SELECTOR, BIT_16], - [PS_CLASS_SELECTOR, BIT_32] -]); -const KEYS_PS_CLASS_STATE = new Set([ - 'checked', - 'closed', - 'disabled', - 'empty', - 'enabled', - 'in-range', - 'indeterminate', - 'invalid', - 'open', - 'out-of-range', - 'placeholder-shown', - 'read-only', - 'read-write', - 'valid' -]); -const KEYS_SHADOW_HOST = new Set(['host', 'host-context']); -const REG_EMPTY_PS_FUNC = - /(?<=:(?:dir|has|host(?:-context)?|is|lang|not|nth-(?:last-)?(?:child|of-type)|where))\(\s+\)/g; -const REG_SHADOW_PS_ELEMENT = /^part|slotted$/; -const U_FFFD = '\uFFFD'; - -/** - * Unescapes a CSS selector string. - * @param {string} selector - The CSS selector to unescape. - * @returns {string} The unescaped selector string. - */ -export const unescapeSelector = (selector = '') => { - if (typeof selector === 'string' && selector.indexOf('\\', 0) >= 0) { - const arr = selector.split('\\'); - const selectorItems = [arr[0]]; - const l = arr.length; - for (let i = 1; i < l; i++) { - const item = arr[i]; - if (item === '' && i === l - 1) { - selectorItems.push(U_FFFD); - } else { - const hexExists = /^([\da-f]{1,6}\s?)/i.exec(item); - if (hexExists) { - const [, hex] = hexExists; - let str; - try { - const low = parseInt('D800', HEX); - const high = parseInt('DFFF', HEX); - const deci = parseInt(hex, HEX); - if (deci === 0 || (deci >= low && deci <= high)) { - str = U_FFFD; - } else { - str = String.fromCodePoint(deci); - } - } catch (e) { - str = U_FFFD; - } - let postStr = ''; - if (item.length > hex.length) { - postStr = item.substring(hex.length); - } - selectorItems.push(`${str}${postStr}`); - // whitespace - } else if (/^[\n\r\f]/.test(item)) { - selectorItems.push(`\\${item}`); - } else { - selectorItems.push(item); - } - } - } - return selectorItems.join(''); - } - return selector; -}; - -/** - * Preprocesses a selector string according to the specification. - * @see https://drafts.csswg.org/css-syntax-3/#input-preprocessing - * @param {string} value - The value to preprocess. - * @returns {string} The preprocessed selector string. - */ -export const preprocess = value => { - // Non-string values will be converted to string. - if (typeof value !== 'string') { - if (value === undefined || value === null) { - return getType(value).toLowerCase(); - } else if (Array.isArray(value)) { - return value.join(','); - } else if (Object.hasOwn(value, 'toString')) { - return value.toString(); - } else { - throw new DOMException(`Invalid selector ${value}`, SYNTAX_ERR); - } - } - let selector = value; - let index = 0; - while (index >= 0) { - // @see https://drafts.csswg.org/selectors/#id-selectors - index = selector.indexOf('#', index); - if (index < 0) { - break; - } - const preHash = selector.substring(0, index + 1); - let postHash = selector.substring(index + 1); - const codePoint = postHash.codePointAt(0); - if (codePoint > BIT_FFFF) { - const str = `\\${codePoint.toString(HEX)} `; - if (postHash.length === DUO) { - postHash = str; - } else { - postHash = `${str}${postHash.substring(DUO)}`; - } - } - selector = `${preHash}${postHash}`; - index++; - } - return selector - .replace(/\f|\r\n?/g, '\n') - .replace(/[\0\uD800-\uDFFF]|\\$/g, U_FFFD) - .replace(/\x26/g, ':scope'); -}; - -/** - * Creates an Abstract Syntax Tree (AST) from a CSS selector string. - * @param {string} sel - The CSS selector string. - * @returns {object} The parsed AST object. - */ -export const parseSelector = sel => { - const selector = preprocess(sel); - // invalid selectors - if (/^$|^\s*>|,\s*$/.test(selector)) { - throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR); - } - try { - const ast = cssTree.parse(selector, { - context: 'selectorList', - parseCustomProperty: true - }); - return cssTree.toPlainObject(ast); - } catch (e) { - const { message } = e; - if ( - /^(?:"\]"|Attribute selector [()\s,=~^$*|]+) is expected$/.test( - message - ) && - !selector.endsWith(']') - ) { - const index = selector.lastIndexOf('['); - const selPart = selector.substring(index); - if (selPart.includes('"')) { - const quotes = selPart.match(/"/g).length; - if (quotes % 2) { - return parseSelector(`${selector}"]`); - } - return parseSelector(`${selector}]`); - } - return parseSelector(`${selector}]`); - } else if (message === '")" is expected') { - // workaround for https://github.com/csstree/csstree/issues/283 - if (REG_EMPTY_PS_FUNC.test(selector)) { - return parseSelector(`${selector.replaceAll(REG_EMPTY_PS_FUNC, '()')}`); - } else if (!selector.endsWith(')')) { - return parseSelector(`${selector})`); - } else { - throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR); - } - } else { - throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR); - } - } -}; - -/** - * Walks the provided AST to collect selector branches and gather information - * about its contents. - * @param {object} ast - The AST to traverse. - * @returns {{branches: Array<object>, info: object}} An object containing the selector branches and info. - */ -export const walkAST = (ast = {}) => { - const branches = new Set(); - const info = { - hasForgivenPseudoFunc: false, - hasHasPseudoFunc: false, - hasLogicalPseudoFunc: false, - hasNotPseudoFunc: false, - hasNthChildOfSelector: false, - hasNestedSelector: false, - hasStatePseudoClass: false - }; - const opt = { - enter(node) { - switch (node.type) { - case CLASS_SELECTOR: { - if (/^-?\d/.test(node.name)) { - throw new DOMException( - `Invalid selector .${node.name}`, - SYNTAX_ERR - ); - } - break; - } - case ID_SELECTOR: { - if (/^-?\d/.test(node.name)) { - throw new DOMException( - `Invalid selector #${node.name}`, - SYNTAX_ERR - ); - } - break; - } - case PS_CLASS_SELECTOR: { - if (KEYS_LOGICAL.has(node.name)) { - info.hasNestedSelector = true; - info.hasLogicalPseudoFunc = true; - if (node.name === 'has') { - info.hasHasPseudoFunc = true; - } else if (node.name === 'not') { - info.hasNotPseudoFunc = true; - } else { - info.hasForgivenPseudoFunc = true; - } - } else if (KEYS_PS_CLASS_STATE.has(node.name)) { - info.hasStatePseudoClass = true; - } else if ( - KEYS_SHADOW_HOST.has(node.name) && - Array.isArray(node.children) && - node.children.length - ) { - info.hasNestedSelector = true; - } - break; - } - case PS_ELEMENT_SELECTOR: { - if (REG_SHADOW_PS_ELEMENT.test(node.name)) { - info.hasNestedSelector = true; - } - break; - } - case NTH: { - if (node.selector) { - info.hasNestedSelector = true; - info.hasNthChildOfSelector = true; - } - break; - } - case SELECTOR: { - branches.add(node.children); - break; - } - default: - } - } - }; - cssTree.walk(ast, opt); - if (info.hasNestedSelector === true) { - cssTree.findAll(ast, (node, item, list) => { - if (list) { - if (node.type === PS_CLASS_SELECTOR && KEYS_LOGICAL.has(node.name)) { - const itemList = list.filter(i => { - const { name, type } = i; - return type === PS_CLASS_SELECTOR && KEYS_LOGICAL.has(name); - }); - for (const { children } of itemList) { - // SelectorList - for (const { children: grandChildren } of children) { - // Selector - for (const { children: greatGrandChildren } of grandChildren) { - if (branches.has(greatGrandChildren)) { - branches.delete(greatGrandChildren); - } - } - } - } - } else if ( - node.type === PS_CLASS_SELECTOR && - KEYS_SHADOW_HOST.has(node.name) && - Array.isArray(node.children) && - node.children.length - ) { - const itemList = list.filter(i => { - const { children, name, type } = i; - const res = - type === PS_CLASS_SELECTOR && - KEYS_SHADOW_HOST.has(name) && - Array.isArray(children) && - children.length; - return res; - }); - for (const { children } of itemList) { - // Selector - for (const { children: grandChildren } of children) { - if (branches.has(grandChildren)) { - branches.delete(grandChildren); - } - } - } - } else if ( - node.type === PS_ELEMENT_SELECTOR && - REG_SHADOW_PS_ELEMENT.test(node.name) - ) { - const itemList = list.filter(i => { - const { name, type } = i; - const res = - type === PS_ELEMENT_SELECTOR && REG_SHADOW_PS_ELEMENT.test(name); - return res; - }); - for (const { children } of itemList) { - // Selector - for (const { children: grandChildren } of children) { - if (branches.has(grandChildren)) { - branches.delete(grandChildren); - } - } - } - } else if (node.type === NTH && node.selector) { - const itemList = list.filter(i => { - const { selector, type } = i; - const res = type === NTH && selector; - return res; - }); - for (const { selector } of itemList) { - const { children } = selector; - // Selector - for (const { children: grandChildren } of children) { - if (branches.has(grandChildren)) { - branches.delete(grandChildren); - } - } - } - } - } - }); - } - return { - info, - branches: [...branches] - }; -}; - -/** - * Comparison function for sorting AST nodes based on specificity. - * @param {object} a - The first AST node. - * @param {object} b - The second AST node. - * @returns {number} -1, 0 or 1, depending on the sort order. - */ -export const compareASTNodes = (a, b) => { - const bitA = AST_SORT_ORDER.get(a.type); - const bitB = AST_SORT_ORDER.get(b.type); - if (bitA === bitB) { - return 0; - } else if (bitA > bitB) { - return 1; - } else { - return -1; - } -}; - -/** - * Sorts a collection of AST nodes based on CSS specificity rules. - * @param {Array<object>} asts - A collection of AST nodes to sort. - * @returns {Array<object>} A new array containing the sorted AST nodes. - */ -export const sortAST = asts => { - const arr = [...asts]; - if (arr.length > 1) { - arr.sort(compareASTNodes); - } - return arr; -}; - -/** - * Parses a type selector's name, which may include a namespace prefix. - * @param {string} selector - The type selector name (e.g., 'ns|E' or 'E'). - * @returns {{prefix: string, localName: string}} An object with `prefix` and - * `localName` properties. - */ -export const parseAstName = selector => { - let prefix; - let localName; - if (selector && typeof selector === 'string') { - if (selector.indexOf('|') > -1) { - [prefix, localName] = selector.split('|'); - } else { - prefix = '*'; - localName = selector; - } - } else { - throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR); - } - return { - prefix, - localName - }; -}; - -/* Re-exported from css-tree. */ -export { find as findAST, generate as generateCSS } from 'css-tree'; |
