UNPKG

postcss-query-ast

Version:
564 lines (530 loc) 20.1 kB
'use strict'; var postcss = require('postcss'); var parser = require('postcss-selector-parser'); var Container = require('postcss/lib/container'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var postcss__namespace = /*#__PURE__*/_interopNamespace(postcss); var parser__default = /*#__PURE__*/_interopDefaultLegacy(parser); var Container__default = /*#__PURE__*/_interopDefaultLegacy(Container); const NTH_CHILD_QUERY_REGEX = /^(-)?(\d*)?n$/; // eslint-disable-line unicorn/no-unsafe-regex /** * @typedef {import('postcss-selector-parser').Root} selectorParser.Root * @typedef {import('postcss-selector-parser').Node} selectorParser.Node */ /** * @param {selectorParser.Root} selectors */ function normalizeTagSelector(selectors) { selectors.walk(( /** @type {selectorParser.Node} */selector, /** @type {number} */index) => { if (['selector', 'tag', 'universal'].includes(selector.type)) { return; } let referenceSelector = null; if (index === 0 && selector.type !== 'combinator') { referenceSelector = selector; } if (index !== 0 && selector.type === 'combinator') { referenceSelector = selector.next(); } if (referenceSelector !== null && !['tag', 'universal'].includes(referenceSelector.type)) { var _selector$parent; selector === null || selector === void 0 || (_selector$parent = selector.parent) === null || _selector$parent === void 0 ? void 0 : _selector$parent.insertBefore(referenceSelector, parser__default["default"].universal()); } }); } /** * @param {selectorParser.Root} selectors */ function normalizeNthChildSelector(selectors) { selectors.walkTags(number => { var _number$parent; if (((_number$parent = number.parent) === null || _number$parent === void 0 || (_number$parent = _number$parent.parent) === null || _number$parent === void 0 ? void 0 : _number$parent.type) !== 'pseudo') { return; } const position = Number(number.value); const previousSelector = number.prev(); const nextSelector = number.next(); if (number.type === 'tag' && Number.isInteger(position) && position >= 0 && !previousSelector && !nextSelector) { number.parent.insertBefore(number, parser__default["default"].tag({ value: '0n' })); number.parent.insertBefore(number, parser__default["default"].combinator({ value: '+' })); number.parent.insertBefore(number, parser__default["default"].tag({ value: String(position) })); number.remove(); } if (number.type === 'tag' && number.value === 'odd') { number.parent.insertBefore(number, parser__default["default"].tag({ value: '2n' })); number.parent.insertBefore(number, parser__default["default"].combinator({ value: '+' })); number.parent.insertBefore(number, parser__default["default"].tag({ value: '1' })); number.remove(); } if (number.type === 'tag' && number.value === 'even') { number.parent.insertBefore(number, parser__default["default"].tag({ value: '2n' })); number.parent.insertBefore(number, parser__default["default"].combinator({ value: '+' })); number.parent.insertBefore(number, parser__default["default"].tag({ value: '2' })); number.remove(); } if (NTH_CHILD_QUERY_REGEX.test(number.value)) { var _number$value$match; const [, negative = '', regexNumber = 1] = (_number$value$match = number.value.match(NTH_CHILD_QUERY_REGEX)) !== null && _number$value$match !== void 0 ? _number$value$match : []; const isAlone = !nextSelector; const isInvalidCombinator = nextSelector && nextSelector.type !== 'combinator' && nextSelector.value !== '+'; if (isInvalidCombinator) { number.parent.insertBefore(number, parser__default["default"].tag({ value: '-n' })); number.parent.insertBefore(number, parser__default["default"].combinator({ value: '+' })); number.parent.insertBefore(number, parser__default["default"].tag({ value: String(0) })); } else { const value = isAlone ? `${regexNumber}n` : `${negative}${regexNumber}n`; number.parent.insertBefore(number, parser__default["default"].tag({ value })); if (isAlone && negative === '') { number.parent.insertBefore(number, parser__default["default"].combinator({ value: '+' })); number.parent.insertBefore(number, parser__default["default"].tag({ value: String(regexNumber) })); } } number.remove(); } }); } /** * @param {string} query */ var getSelectorAst = (query => { return parser__default["default"](selectors => { normalizeTagSelector(selectors); normalizeNthChildSelector(selectors); }).ast(query); }); /** * @typedef {import('postcss').ChildNode} postcss.ChildNode */ class PassthroughContainer extends Container__default["default"] { constructor() { super(); this.type = 'passthrough-container'; /** @type {postcss.ChildNode[]} */ this.nodes = []; } /** * @param {postcss.ChildNode[]} nodes */ normalize(nodes) { return [nodes]; } } /** * @typedef {import('postcss').ChildNode} postcss.ChildNode * @typedef {import('postcss-selector-parser').Combinator} selectorParser.Combinator * @typedef {import('postcss-selector-parser').Node} selectorParser.Node */ /** * @param {postcss.ChildNode} node * @param {selectorParser.Node} selector */ function isValidNode(node, selector) { return selector && (selector.type === 'tag' && selector.value === node.type || selector.type === 'universal'); } /** * @param {postcss.ChildNode} node * @param {selectorParser.Combinator} selector */ var getCombinator = ((node, selector) => { var _node$parent; const container = new PassthroughContainer(); const result = []; const nodeIndex = (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.index(node); const nextNode = node.next(); const nextSelector = selector.next(); const isNodeContainer = node instanceof Container__default["default"]; if (selector.value === ' ' && isNodeContainer) { node.walk(resolvedNode => { container.append(resolvedNode); }); result.push(container); } if (selector.value === '+') { if (typeof nextNode !== 'undefined' && isValidNode(nextNode, nextSelector)) { container.append(nextNode); } result.push(container); } if (selector.value === '~') { var _node$parent2; (_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.each((resolvedNode, index) => { if (typeof nodeIndex !== 'undefined' && index > nodeIndex && isValidNode(resolvedNode, nextSelector)) { container.append(resolvedNode); } }); result.push(container); } if (selector.value === '>' && isNodeContainer) { node.each(resolvedNode => { if (nextSelector && isValidNode(resolvedNode, nextSelector)) { container.append(resolvedNode); } }); result.push(container); } return result; }); /** * @typedef {import('postcss').ChildNode} postcss.ChildNode * @typedef {import('postcss').Root} postcss.Root * @typedef {import('postcss-selector-parser').Tag} selectorParser.Tag * @typedef {import('postcss-selector-parser').Universal} selectorParser.Universal * @typedef {import('../passthrough-container').default} PassthroughContainer */ /** * @param {postcss.ChildNode[]} result */ function addNodeToResult(result) { /** * @param {postcss.ChildNode} resolvedNode */ return resolvedNode => { result.push(resolvedNode); }; } /** * @param {postcss.Root|PassthroughContainer} node * @param {selectorParser.Tag|selectorParser.Universal} selector */ var getTag = ((node, selector) => { const previousSelector = selector.prev(); /** @type {postcss.ChildNode[]} */ const result = []; const isPreviousSelectorCombinator = previousSelector && previousSelector.type === 'combinator' && ['>', '+', '~'].includes(previousSelector.value); const callback = addNodeToResult(result); if (selector.type === 'universal') { if (isPreviousSelectorCombinator) { node.each(callback); } else { node.walk(callback); } } if (selector.type === 'tag') { if (isPreviousSelectorCombinator) { node.each(resolvedNode => { if (selector.value === resolvedNode.type) { callback(resolvedNode); } }); } else { switch (selector.value) { case 'decl': node.walkDecls(callback); break; case 'atrule': node.walkAtRules(callback); break; case 'rule': node.walkRules(callback); break; case 'comment': node.walkComments(callback); break; } } } return result; }); /** * @typedef {import('postcss').ChildNode} postcss.ChildNode * @typedef {import('postcss-selector-parser').Attribute} selectorParser.Attribute * @typedef {import('postcss-selector-parser').Node} selectorParser.Node */ const REGEX_REGEX = /^\/(.+)\/([gimuy]+)?$/; // eslint-disable-line unicorn/no-unsafe-regex /** * @param {string} regex * @param {string} flags * @param {postcss.ChildNode} node * @param {selectorParser.Attribute} selector */ function isRegexMatched(regex, flags, node, selector) { /** @type {string} */ // @ts-ignore const composedAttribute = node[selector.attribute]; return new RegExp(regex, flags).test(composedAttribute); } /** * @param {selectorParser.Attribute} selector */ function whitespaceSeparatedWordTest(selector) { /** * @param {string} value */ return value => { if (selector.insensitive) { var _selector$value; return value.toLowerCase() === ((_selector$value = selector.value) === null || _selector$value === void 0 ? void 0 : _selector$value.toLowerCase()); } return value === selector.value; }; } /** * @param {string} operator * @param {string} value */ function getRegexByOperator(operator, value) { switch (operator) { case '|=': return `^${value}-?`; case '^=': return `^${value}`; case '$=': return `${value}$`; case '*=': default: return `${value}`; } } /** * @param {postcss.ChildNode} node * @param {selectorParser.Attribute} selector */ var getAttribute = ((node, selector) => { var _selector$value2; /** @type {postcss.ChildNode[]} */ const result = []; const hasSelectorAttribute = (selector.attribute in node); if (hasSelectorAttribute && (typeof selector.operator === 'undefined' || typeof selector.value === 'undefined')) { result.push(node); } if (hasSelectorAttribute && selector.operator === '=' && selector.quoted && typeof selector.value !== 'undefined' && REGEX_REGEX.test(selector.value)) { var _selector$value$match; const [, regex, flags = ''] = (_selector$value$match = selector.value.match(REGEX_REGEX)) !== null && _selector$value$match !== void 0 ? _selector$value$match : []; if (isRegexMatched(regex, flags, node, selector)) { result.push(node); } } /** @type {string} */ // @ts-ignore const composedAttribute = node[selector.attribute]; if (hasSelectorAttribute && selector.operator === '=' && (composedAttribute === selector.value && !selector.insensitive || composedAttribute.toLowerCase() === ((_selector$value2 = selector.value) === null || _selector$value2 === void 0 ? void 0 : _selector$value2.toLowerCase()) && selector.insensitive)) { result.push(node); } if (hasSelectorAttribute && selector.operator === '~=' && composedAttribute.split(' ').some(whitespaceSeparatedWordTest(selector))) { result.push(node); } if (hasSelectorAttribute && typeof selector.operator !== 'undefined' && ['|=', '^=', '$=', '*='].includes(selector.operator)) { var _selector$value3; const flags = selector.insensitive ? 'i' : ''; const regex = getRegexByOperator(selector.operator, (_selector$value3 = selector.value) !== null && _selector$value3 !== void 0 ? _selector$value3 : ''); if (regex !== null && isRegexMatched(regex, flags, node, selector)) { result.push(node); } } return result; }); /** * @typedef {import('postcss').ChildNode} postcss.ChildNode * @typedef {import('postcss-selector-parser').Pseudo} selectorParser.Pseudo * @typedef {import('postcss-selector-parser').Root} selectorParser.Root * @typedef {import('../../index').ProcessSelectors} ProcessSelectors */ /** * @param {selectorParser.Pseudo} selector * @param {postcss.ChildNode[]} siblings */ function processNthNodeQuery(selector, siblings) { var _first$match; const [first,, third] = selector.first.map(({ value }) => value); const [, negative = '', number] = (_first$match = first === null || first === void 0 ? void 0 : first.match(NTH_CHILD_QUERY_REGEX)) !== null && _first$match !== void 0 ? _first$match : []; const parsedNumber = Number(number); const parsedThird = Number(third); const range = new Array(Number(negative !== '' ? parsedThird : siblings.length)).fill(0).map((value, index) => value + index); const indices = range.map(index => { if (negative !== '') { return -(parsedNumber * index) + parsedThird; } return parsedNumber * index + parsedThird; }).map(index => index - 1).filter(index => index >= 0 && index <= siblings.length - 1); const uniqueIndices = [...new Set(indices)]; return uniqueIndices; } /** * @param {postcss.ChildNode} node * @param {selectorParser.Pseudo} selector * @param {ProcessSelectors} processSelectors */ var getPseudo = ((node, selector, processSelectors) => { var _node$parent, _node$parent2, _node$parent3; const result = []; if (selector.value === ':empty' && 'nodes' in node && node.nodes.length === 0) { result.push(node); } if (selector.value === ':only-child' && ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.nodes.length) === 1 && node.parent.nodes[0] === node) { result.push(node); } if (selector.value === ':first-child' && ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.first) === node) { result.push(node); } if (selector.value === ':last-child' && ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.last) === node) { result.push(node); } if (selector.value === ':nth-child' || selector.value === ':nth-last-child') { var _node$parent$nodes, _node$parent4, _node$parent$nodes$le, _node$parent5; const indices = processNthNodeQuery(selector, (_node$parent$nodes = (_node$parent4 = node.parent) === null || _node$parent4 === void 0 ? void 0 : _node$parent4.nodes) !== null && _node$parent$nodes !== void 0 ? _node$parent$nodes : []); const nodesCount = ((_node$parent$nodes$le = (_node$parent5 = node.parent) === null || _node$parent5 === void 0 ? void 0 : _node$parent5.nodes.length) !== null && _node$parent$nodes$le !== void 0 ? _node$parent$nodes$le : 0) - 1; indices.forEach(index => { var _node$parent6; const nodeIndex = selector.value === ':nth-child' ? index : nodesCount - index; if (((_node$parent6 = node.parent) === null || _node$parent6 === void 0 ? void 0 : _node$parent6.nodes[nodeIndex]) === node) { result.push(node); } }); } if (selector.value === ':first-of-type' || selector.value === ':last-of-type' || selector.value === ':only-of-type' || selector.value === ':nth-of-type' || selector.value === ':nth-last-of-type') { var _node$parent7; /** @type {postcss.ChildNode[]} */ const typeResult = []; (_node$parent7 = node.parent) === null || _node$parent7 === void 0 ? void 0 : _node$parent7.each(resolvedNode => { if (resolvedNode.type === node.type) { typeResult.push(resolvedNode); } }); const nodesCount = typeResult.length; typeResult.forEach((resolvedTypeResultNode, index) => { if (selector.value === ':first-of-type' && index === 0 && resolvedTypeResultNode === node) { result.push(node); } if (selector.value === ':last-of-type' && index === nodesCount - 1 && resolvedTypeResultNode === node) { result.push(node); } if (selector.value === ':only-of-type' && nodesCount === 1 && resolvedTypeResultNode === node) { result.push(node); } }); if (selector.value === ':nth-of-type' || selector.value === ':nth-last-of-type') { const indices = processNthNodeQuery(selector, typeResult); const nthNodesCount = typeResult.length - 1; indices.forEach(index => { const nodeIndex = selector.value === ':nth-of-type' ? index : nthNodesCount - index; if (typeResult[nodeIndex] === node) { result.push(node); } }); } } if (selector.value === ':not' || selector.value === ':matches') { const container = new PassthroughContainer(); container.append(node); const processSelectorsResult = processSelectors(selector, container); if (selector.value === ':not') { if (!processSelectorsResult.includes(node)) { result.push(node); } } else { processSelectorsResult.forEach(resolvedProcessSelectorsResultNode => { result.push(resolvedProcessSelectorsResultNode); }); } } return result; }); /* eslint-disable import/no-namespace */ /** * @typedef {( * selectors: selectorParser.Container|selectorParser.Root, * ast: postcss.Root|postcss.ChildNode|PassthroughContainer * ) => (postcss.Root|postcss.ChildNode|PassthroughContainer)[]} ProcessSelectors */ /** * @type {ProcessSelectors} */ function processSelectors(selectors, ast) { const nodes = selectors.map(rootSelector => { if (!parser__default["default"].isSelector(rootSelector)) { return []; } return rootSelector.reduce((astContainer, selector) => { return astContainer.map(node => { if (node instanceof postcss__namespace.Root || node instanceof PassthroughContainer) { switch (selector.type) { case 'tag': case 'universal': return getTag(node, selector); default: return []; } } switch (selector.type) { case 'combinator': return getCombinator(node, selector); case 'attribute': return getAttribute(node, selector); case 'pseudo': return getPseudo(node, selector, processSelectors); default: return []; } }).reduce(( /** @type {(postcss.ChildNode|PassthroughContainer)[]}*/array, result) => [...array, ...result], []).filter(result => result !== null); }, [ast]); }).reduce((array, result) => [...array, ...result], []); const uniqueNodes = [...new Set(nodes)]; return uniqueNodes; } /** * Queries PostCSS with CSS selector. * * @param {string} query CSS selector. * @param {postcss.Root} ast PostCSS AST. */ async function index (query, ast) { const selectorAst = await getSelectorAst(query); const result = []; for (const node of processSelectors(selectorAst, ast)) { if (!(node instanceof PassthroughContainer)) { result.push(node); } } return result; } module.exports = index; //# sourceMappingURL=index.js.map