aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/parser.js
diff options
context:
space:
mode:
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.js431
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';