eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
330 lines (328 loc) • 12 kB
JavaScript
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;