UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

330 lines (328 loc) 12 kB
const require_runtime = require('../_virtual/_rolldown/runtime.js'); const require_index = require('./index.js'); let postcss_selector_parser = require("postcss-selector-parser"); postcss_selector_parser = require_runtime.__toESM(postcss_selector_parser); let nth_check = require("nth-check"); nth_check = require_runtime.__toESM(nth_check); //#region lib/utils/selector.ts var import_utils = /* @__PURE__ */ require_runtime.__toESM(require_index.default); /** * Parses CSS selectors and returns an object with a function that tests VElement. */ function parseSelector(selector, context) { let astSelector; try { astSelector = (0, postcss_selector_parser.default)().astSync(selector); } catch { context.report({ loc: { line: 0, column: 0 }, message: `Cannot parse selector: ${selector}.` }); return { test: () => false }; } try { const test = selectorsToVElementMatcher(astSelector.nodes); return { test(element) { return test(element, null); } }; } catch (error) { if (error instanceof SelectorError) { context.report({ loc: { line: 0, column: 0 }, message: error.message }); return { test: () => false }; } throw error; } } var SelectorError = class extends Error {}; /** * Convert nodes to VElementMatcher */ function selectorsToVElementMatcher(selectorNodes) { const selectors = selectorNodes.map((n) => selectorToVElementMatcher(cleanSelectorChildren(n))); return (element, subject) => selectors.some((sel) => sel(element, subject)); } function isDescendantCombinator(node) { return Boolean(node && node.type === "combinator" && !node.value.trim()); } /** * Clean and get the selector child nodes. */ function cleanSelectorChildren(selector) { const nodes = []; let last = null; for (const node of selector.nodes) { if (node.type === "root") throw new SelectorError("Unexpected state type=root"); if (node.type === "comment") continue; if ((last == null || last.type === "combinator") && isDescendantCombinator(node)) continue; if (isDescendantCombinator(last) && node.type === "combinator") nodes.pop(); nodes.push(node); last = node; } if (isDescendantCombinator(last)) nodes.pop(); return nodes; } /** * Convert Selector child nodes to VElementMatcher */ function selectorToVElementMatcher(selectorChildren) { const nodes = [...selectorChildren]; let node = nodes.shift(); let result = null; while (node) { if (node.type === "combinator") { const combinator = node.value; node = nodes.shift(); if (!node) throw new SelectorError(`Expected selector after '${combinator}'.`); if (node.type === "combinator") throw new SelectorError(`Unexpected combinator '${node.value}'.`); const right = nodeToVElementMatcher(node); result = combination(result || ((element, subject) => element === subject), combinator, right); } else { const sel = nodeToVElementMatcher(node); result = result ? compound(result, sel) : sel; } node = nodes.shift(); } if (!result) throw new SelectorError(`Unexpected empty selector.`); return result; } function combination(left, combinator, right) { switch (combinator.trim()) { case "": return (element, subject) => { if (right(element, null)) { let parent = element.parent; while (parent.type === "VElement") { if (left(parent, subject)) return true; parent = parent.parent; } } return false; }; case ">": return (element, subject) => { if (right(element, null)) { const parent = element.parent; if (parent.type === "VElement") return left(parent, subject); } return false; }; case "+": return (element, subject) => { if (right(element, null)) { const before = getBeforeElement(element); if (before) return left(before, subject); } return false; }; case "~": return (element, subject) => { if (right(element, null)) { for (const before of getBeforeElements(element)) if (left(before, subject)) return true; } return false; }; default: throw new SelectorError(`Unknown combinator: ${combinator}.`); } } /** * Convert node to VElementMatcher */ function nodeToVElementMatcher(selector) { switch (selector.type) { case "attribute": return attributeNodeToVElementMatcher(selector); case "class": return classNameNodeToVElementMatcher(selector); case "id": return identifierNodeToVElementMatcher(selector); case "tag": return tagNodeToVElementMatcher(selector); case "universal": return universalNodeToVElementMatcher(selector); case "pseudo": return pseudoNodeToVElementMatcher(selector); case "nesting": throw new SelectorError("Unsupported nesting selector."); case "string": throw new SelectorError(`Unknown selector: ${selector.value}.`); default: throw new SelectorError(`Unknown selector: ${selector.value}.`); } } /** * Convert Attribute node to VElementMatcher */ function attributeNodeToVElementMatcher(selector) { const key = selector.attribute; if (!selector.operator) return (element) => getAttributeValue(element, key) != null; const value = selector.value || ""; switch (selector.operator) { case "=": return buildVElementMatcher(value, (attr, val) => attr === val); case "~=": return buildVElementMatcher(value, (attr, val) => attr.split(/\s+/gu).includes(val)); case "|=": return buildVElementMatcher(value, (attr, val) => attr === val || attr.startsWith(`${val}-`)); case "^=": return buildVElementMatcher(value, (attr, val) => attr.startsWith(val)); case "$=": return buildVElementMatcher(value, (attr, val) => attr.endsWith(val)); case "*=": return buildVElementMatcher(value, (attr, val) => attr.includes(val)); default: throw new SelectorError(`Unsupported operator: ${selector.operator}.`); } function buildVElementMatcher(selectorValue, test) { const val = selector.insensitive ? selectorValue.toLowerCase() : selectorValue; return (element) => { const attrValue = getAttributeValue(element, key); if (attrValue == null) return false; return test(selector.insensitive ? attrValue.toLowerCase() : attrValue, val); }; } } /** * Convert ClassName node to VElementMatcher */ function classNameNodeToVElementMatcher(selector) { const className = selector.value; return (element) => { const attrValue = getAttributeValue(element, "class"); if (attrValue == null) return false; return attrValue.split(/\s+/gu).includes(className); }; } /** * Convert Identifier node to VElementMatcher */ function identifierNodeToVElementMatcher(selector) { const id = selector.value; return (element) => { const attrValue = getAttributeValue(element, "id"); if (attrValue == null) return false; return attrValue === id; }; } /** * Convert Tag node to VElementMatcher */ function tagNodeToVElementMatcher(selector) { const name = selector.value; return (element) => element.rawName === name; } /** * Convert Universal node to VElementMatcher */ function universalNodeToVElementMatcher(_selector) { return () => true; } /** * Convert Pseudo node to VElementMatcher */ function pseudoNodeToVElementMatcher(selector) { const pseudo = selector.value; switch (pseudo) { case ":not": { const selectors = selectorsToVElementMatcher(selector.nodes); return (element, subject) => !selectors(element, subject); } case ":is": case ":where": return selectorsToVElementMatcher(selector.nodes); case ":has": return pseudoHasSelectorsToVElementMatcher(selector.nodes); case ":empty": return (element) => element.children.every((child) => child.type === "VText" && !child.value.trim()); case ":nth-child": return buildPseudoNthVElementMatcher(parseNth(selector)); case ":nth-last-child": { const nth = parseNth(selector); return buildPseudoNthVElementMatcher((index, length) => nth(length - index - 1)); } case ":first-child": return buildPseudoNthVElementMatcher((index) => index === 0); case ":last-child": return buildPseudoNthVElementMatcher((index, length) => index === length - 1); case ":only-child": return buildPseudoNthVElementMatcher((index, length) => index === 0 && length === 1); case ":nth-of-type": return buildPseudoNthOfTypeVElementMatcher(parseNth(selector)); case ":nth-last-of-type": { const nth = parseNth(selector); return buildPseudoNthOfTypeVElementMatcher((index, length) => nth(length - index - 1)); } case ":first-of-type": return buildPseudoNthOfTypeVElementMatcher((index) => index === 0); case ":last-of-type": return buildPseudoNthOfTypeVElementMatcher((index, length) => index === length - 1); case ":only-of-type": return buildPseudoNthOfTypeVElementMatcher((index, length) => index === 0 && length === 1); default: throw new SelectorError(`Unsupported pseudo selector: ${pseudo}.`); } } /** * Convert :has() selector nodes to VElementMatcher */ function pseudoHasSelectorsToVElementMatcher(selectorNodes) { const selectors = selectorNodes.map((n) => pseudoHasSelectorToVElementMatcher(n)); return (element, subject) => selectors.some((sel) => sel(element, subject)); } /** * Convert :has() selector node to VElementMatcher */ function pseudoHasSelectorToVElementMatcher(selector) { const nodes = cleanSelectorChildren(selector); const selectors = selectorToVElementMatcher(nodes); const firstNode = nodes[0]; if (firstNode.type === "combinator" && (firstNode.value === "+" || firstNode.value === "~")) return buildVElementMatcher(selectors, (element) => getAfterElements(element)); return buildVElementMatcher(selectors, (element) => element.children.filter(import_utils.default.isVElement)); } function buildVElementMatcher(selectors, getStartElements) { return (element) => { const elements = [...getStartElements(element)]; let curr; while (curr = elements.shift()) { const el = curr; if (selectors(el, element)) return true; elements.push(...el.children.filter(import_utils.default.isVElement)); } return false; }; } /** * Parse <nth> */ function parseNth(pseudoNode) { const argumentsText = pseudoNode.toString().slice(pseudoNode.value.length).toLowerCase(); const openParenIndex = argumentsText.indexOf("("); const closeParenIndex = argumentsText.lastIndexOf(")"); if (openParenIndex === -1 || closeParenIndex === -1) throw new SelectorError(`Cannot parse An+B micro syntax (:nth-xxx() argument): ${argumentsText}.`); const argument = argumentsText.slice(openParenIndex + 1, closeParenIndex).trim(); try { return (0, nth_check.default)(argument); } catch { throw new SelectorError(`Cannot parse An+B micro syntax (:nth-xxx() argument): '${argument}'.`); } } /** * Build VElementMatcher for :nth-xxx() */ function buildPseudoNthVElementMatcher(testIndex) { return (element) => { const elements = element.parent.children.filter(import_utils.default.isVElement); return testIndex(elements.indexOf(element), elements.length); }; } /** * Build VElementMatcher for :nth-xxx-of-type() */ function buildPseudoNthOfTypeVElementMatcher(testIndex) { return (element) => { const elements = element.parent.children.filter((e) => import_utils.default.isVElement(e) && e.rawName === element.rawName); return testIndex(elements.indexOf(element), elements.length); }; } function getBeforeElement(element) { return getBeforeElements(element).pop() || null; } function getBeforeElements(element) { const parent = element.parent; const index = parent.children.indexOf(element); return parent.children.slice(0, index).filter(import_utils.default.isVElement); } function getAfterElements(element) { const parent = element.parent; const index = parent.children.indexOf(element); return parent.children.slice(index + 1).filter(import_utils.default.isVElement); } function compound(a, b) { return (element, subject) => a(element, subject) && b(element, subject); } /** * Get attribute value from given element. */ function getAttributeValue(element, attribute) { const attr = import_utils.default.getAttribute(element, attribute); if (attr) return attr.value && attr.value.value || ""; return null; } //#endregion exports.parseSelector = parseSelector;