astro-ghostcms/.pnpm-store/v3/files/85/9c8c32a10382912996e784711ea...

654 lines
22 KiB
Plaintext

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("@typescript-eslint/types");
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const postcss_1 = __importDefault(require("postcss"));
const postcss_selector_parser_1 = __importDefault(require("postcss-selector-parser"));
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const transform_1 = require("../utils/transform");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)("no-unused-css-selector", {
meta: {
docs: {
description: "disallow selectors defined in `style` tag that don't use in HTML",
category: "Best Practices",
recommended: false,
},
schema: [],
messages: {
unused: "Unused CSS selector `{{selector}}`",
},
type: "problem",
},
create(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
if (!sourceCode.parserServices.isAstro) {
return {};
}
const styles = [];
const rootTree = {
parent: null,
node: null,
childElements: [],
};
const allTreeElements = [];
let currTree = rootTree;
function verifyCSS(css) {
let root;
try {
root = postcss_1.default.parse(css.css);
}
catch (_e) {
return;
}
const ignoreNodes = new Set();
root.walk((psNode) => {
if (psNode.parent && ignoreNodes.has(psNode.parent)) {
ignoreNodes.add(psNode);
return;
}
if (psNode.type !== "rule") {
if (psNode.type === "atrule") {
if (psNode.name === "keyframes") {
ignoreNodes.add(psNode);
}
}
return;
}
const rule = psNode;
const raws = rule.raws;
const rawSelectorText = raws.selector
? raws.selector.raw
: rule.selector;
for (const selector of parseSelector(rawSelectorText, context)) {
if (selector.error) {
continue;
}
if (allTreeElements.some((tree) => selector.test(tree))) {
continue;
}
reportSelector(rule.source.start.offset + selector.offset, selector.selector);
}
});
function reportSelector(start, selector) {
const remapStart = css.remap(start);
const remapEnd = css.remap(start + selector.length);
context.report({
loc: {
start: sourceCode.getLocFromIndex(remapStart),
end: sourceCode.getLocFromIndex(remapEnd),
},
messageId: "unused",
data: {
selector,
},
});
}
}
return {
JSXElement(node) {
const name = (0, ast_utils_1.getElementName)(node);
if (name === "Fragment" || name === "slot") {
return;
}
if (name === "style" && !(0, ast_utils_1.findAttribute)(node, "is:global")) {
styles.push(node);
}
const tree = {
parent: currTree,
node,
childElements: [],
};
allTreeElements.unshift(tree);
currTree.childElements.push(tree);
currTree = tree;
},
"JSXElement:exit"(node) {
if (currTree.node === node) {
if (currTree.node) {
const expressions = currTree.node.children.filter((e) => e.type === "JSXExpressionContainer");
if (expressions.length) {
for (const child of currTree.childElements) {
child.withinExpression = expressions.some((e) => e.range[0] <= child.node.range[0] &&
child.node.range[1] <= e.range[1]);
}
}
}
currTree = currTree.parent;
}
},
"Program:exit"() {
for (const style of styles) {
const css = (0, transform_1.getStyleContentCSS)(style, context);
if (css) {
verifyCSS(css);
}
}
},
};
},
});
class SelectorError extends Error {
}
function parseSelector(selector, context) {
let astSelector;
try {
astSelector = (0, postcss_selector_parser_1.default)().astSync(selector);
}
catch (error) {
return [
{
error,
selector,
offset: 0,
test: () => false,
},
];
}
return astSelector.nodes.map((sel) => {
var _a, _b;
const nodes = removeGlobals(cleanSelectorChildren(sel));
try {
const test = selectorToJSXElementMatcher(nodes, context);
return {
selector: sel.toString().trim(),
offset: (_a = sel.sourceIndex) !== null && _a !== void 0 ? _a : sel.nodes[0].sourceIndex,
test(element) {
return test(element, null);
},
};
}
catch (error) {
if (error instanceof SelectorError) {
return {
error,
selector: sel.toString().trim(),
offset: (_b = sel.sourceIndex) !== null && _b !== void 0 ? _b : sel.nodes[0].sourceIndex,
test: () => false,
};
}
throw error;
}
});
function removeGlobals(nodes) {
var _a, _b, _c;
let start = 0;
let end = nodes.length;
while (nodes[end - 1] && isGlobalPseudo(nodes[end - 1])) {
end--;
if (((_a = nodes[end - 1]) === null || _a === void 0 ? void 0 : _a.type) === "combinator") {
end--;
}
}
while (nodes[start] && isGlobalPseudo(nodes[start])) {
start++;
if (((_b = nodes[start]) === null || _b === void 0 ? void 0 : _b.type) === "combinator") {
start++;
}
}
if (nodes.some(isRootPseudo)) {
while (nodes[start] && !isRootPseudo(nodes[start])) {
start++;
}
start++;
while (nodes[start] && nodes[start].type !== "combinator") {
start++;
}
if (((_c = nodes[start]) === null || _c === void 0 ? void 0 : _c.type) === "combinator") {
start++;
}
}
return nodes.slice(start, end);
}
}
function selectorsToJSXElementMatcher(selectorNodes, context) {
const selectors = selectorNodes.map((n) => selectorToJSXElementMatcher(cleanSelectorChildren(n), context));
return (element, subject) => selectors.some((sel) => sel(element, subject));
}
function isDescendantCombinator(node) {
return Boolean(node && node.type === "combinator" && !node.value.trim());
}
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;
}
function selectorToJSXElementMatcher(selectorChildren, context) {
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 = nodeToJSXElementMatcher(node, context);
result = combination(result ||
((element, subject) => element === subject), combinator, right);
}
else {
const sel = nodeToJSXElementMatcher(node, context);
result = result ? compound(result, sel) : sel;
}
node = nodes.shift();
}
if (!result) {
return () => true;
}
return result;
}
function combination(left, combinator, right) {
switch (combinator.trim()) {
case "":
return (element, subject) => {
if (right(element, null)) {
let parent = element.parent;
while (parent.node) {
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.node) {
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}.`);
}
}
function nodeToJSXElementMatcher(selector, context) {
const baseMatcher = (() => {
switch (selector.type) {
case "attribute":
return attributeNodeToJSXElementMatcher(selector, context);
case "class":
return classNameNodeToJSXElementMatcher(selector, context);
case "id":
return identifierNodeToJSXElementMatcher(selector, context);
case "tag":
return tagNodeToJSXElementMatcher(selector);
case "universal":
return universalNodeToJSXElementMatcher(selector);
case "pseudo":
return pseudoNodeToJSXElementMatcher(selector, context);
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}.`);
}
})();
return (element, subject) => {
if (isComponentElement(element)) {
return false;
}
return baseMatcher(element, subject);
};
}
function attributeNodeToJSXElementMatcher(selector, context) {
const key = selector.attribute;
if (!selector.operator) {
return (element, _) => {
return hasAttribute(element, key, context);
};
}
const value = selector.value || "";
switch (selector.operator) {
case "=":
return buildJSXElementMatcher(value, (attr, val) => attr === val);
case "~=":
return buildJSXElementMatcher(value, (attr, val) => attr.split(/\s+/u).includes(val));
case "|=":
return buildJSXElementMatcher(value, (attr, val) => attr === val || attr.startsWith(`${val}-`));
case "^=":
return buildJSXElementMatcher(value, (attr, val) => attr.startsWith(val));
case "$=":
return buildJSXElementMatcher(value, (attr, val) => attr.endsWith(val));
case "*=":
return buildJSXElementMatcher(value, (attr, val) => attr.includes(val));
default:
throw new SelectorError(`Unsupported operator: ${selector.operator}.`);
}
function buildJSXElementMatcher(selectorValue, test) {
const val = selector.insensitive
? selectorValue.toLowerCase()
: selectorValue;
return (element) => {
const attr = getAttribute(element, key, context);
if (attr == null) {
return false;
}
if (attr.unknown || !attr.staticValue) {
return true;
}
const attrValue = attr.staticValue.value;
return test(selector.insensitive ? attrValue.toLowerCase() : attrValue, val);
};
}
}
function classNameNodeToJSXElementMatcher(selector, context) {
const className = selector.value;
return (element) => {
const attr = getAttribute(element, "class", context);
if (attr == null) {
return false;
}
if (attr.unknown || !attr.staticValue) {
return true;
}
const attrValue = attr.staticValue.value;
return attrValue.split(/\s+/u).includes(className);
};
}
function identifierNodeToJSXElementMatcher(selector, context) {
const id = selector.value;
return (element) => {
const attr = getAttribute(element, "id", context);
if (attr == null) {
return false;
}
if (attr.unknown || !attr.staticValue) {
return true;
}
const attrValue = attr.staticValue.value;
return attrValue === id;
};
}
function tagNodeToJSXElementMatcher(selector) {
const name = selector.value;
return (element) => {
const elementName = (0, ast_utils_1.getElementName)(element.node);
return elementName === name;
};
}
function universalNodeToJSXElementMatcher(_selector) {
return () => true;
}
function pseudoNodeToJSXElementMatcher(selector, context) {
const pseudo = selector.value;
switch (pseudo) {
case ":is":
case ":where":
return selectorsToJSXElementMatcher(selector.nodes, context);
case ":has":
return pseudoHasSelectorsToJSXElementMatcher(selector.nodes, context);
case ":empty":
return (element) => element.node.children.every((child) => (child.type === "JSXText" && !child.value.trim()) ||
child.type === "AstroHTMLComment");
case ":global": {
return () => true;
}
default:
return () => true;
}
}
function pseudoHasSelectorsToJSXElementMatcher(selectorNodes, context) {
const selectors = selectorNodes.map((n) => pseudoHasSelectorToJSXElementMatcher(n, context));
return (element, subject) => selectors.some((sel) => sel(element, subject));
}
function pseudoHasSelectorToJSXElementMatcher(selector, context) {
const nodes = cleanSelectorChildren(selector);
const selectors = selectorToJSXElementMatcher(nodes, context);
const firstNode = nodes[0];
if (firstNode.type === "combinator" &&
(firstNode.value === "+" || firstNode.value === "~")) {
return buildJSXElementMatcher((element) => getAfterElements(element));
}
return buildJSXElementMatcher((element) => element.childElements);
function buildJSXElementMatcher(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.childElements);
}
return false;
};
}
}
function getBeforeElement(element) {
return getBeforeElements(element).pop() || null;
}
function getBeforeElements(element) {
const parent = element.parent;
if (!parent) {
return [];
}
const index = parent.childElements.indexOf(element);
return parent.childElements.slice(0, element.withinExpression ? index + 1 : index);
}
function getAfterElements(element) {
const parent = element.parent;
if (!parent) {
return [];
}
const index = parent.childElements.indexOf(element);
return parent.childElements.slice(element.withinExpression ? index : index + 1);
}
function compound(a, b) {
return (element, subject) => a(element, subject) && b(element, subject);
}
function isComponentElement(element) {
const elementName = (0, ast_utils_1.getElementName)(element.node);
return elementName == null || elementName.toLowerCase() !== elementName;
}
function isGlobalPseudo(node) {
return node.type === "pseudo" && node.value === ":global";
}
function isRootPseudo(node) {
return node.type === "pseudo" && node.value === ":root";
}
function hasAttribute(element, attribute, context) {
const attr = getAttribute(element, attribute, context);
if (attr) {
return true;
}
return false;
}
function getAttribute(element, attribute, context) {
const attr = (0, ast_utils_1.findAttribute)(element.node, attribute);
if (attr) {
if (attr.value == null) {
return {
unknown: false,
hasAttr: true,
staticValue: { value: "" },
};
}
const value = (0, ast_utils_1.getStaticAttributeStringValue)(attr, context);
if (value == null) {
return {
unknown: false,
hasAttr: true,
staticValue: null,
};
}
return {
unknown: false,
hasAttr: true,
staticValue: { value },
};
}
if (attribute === "class") {
const result = getClassListAttribute(element, context);
if (result) {
return result;
}
}
const spreadAttributes = (0, ast_utils_1.getSpreadAttributes)(element.node);
if (spreadAttributes.length === 0) {
return null;
}
return {
unknown: true,
};
}
function getClassListAttribute(element, context) {
const attr = (0, ast_utils_1.findAttribute)(element.node, "class:list");
if (attr) {
if (attr.value == null) {
return {
unknown: false,
hasAttr: true,
staticValue: { value: "" },
};
}
const classList = extractClassList(attr, context);
if (classList === null) {
return {
unknown: false,
hasAttr: true,
staticValue: null,
};
}
return {
unknown: false,
hasAttr: true,
staticValue: { value: classList.classList.join(" ") },
};
}
return null;
}
function extractClassList(node, context) {
var _a, _b;
if (((_a = node.value) === null || _a === void 0 ? void 0 : _a.type) === types_1.AST_NODE_TYPES.Literal) {
return { classList: [String(node.value.value)] };
}
if (((_b = node.value) === null || _b === void 0 ? void 0 : _b.type) === "JSXExpressionContainer" &&
node.value.expression.type !== "JSXEmptyExpression") {
const classList = [];
for (const className of extractClassListFromExpression(node.value.expression, context)) {
if (className == null) {
return null;
}
classList.push(className);
}
return { classList };
}
return null;
}
function* extractClassListFromExpression(node, context) {
if (node.type === types_1.AST_NODE_TYPES.ArrayExpression) {
for (const element of node.elements) {
if (element == null)
continue;
if (element.type === types_1.AST_NODE_TYPES.SpreadElement) {
yield* extractClassListFromExpression(element.argument, context);
}
else {
yield* extractClassListFromExpression(element, context);
}
}
return;
}
if (node.type === types_1.AST_NODE_TYPES.ObjectExpression) {
for (const prop of node.properties) {
if (prop.type === types_1.AST_NODE_TYPES.SpreadElement) {
yield* extractClassListFromExpression(prop.argument, context);
}
else {
if (!prop.computed) {
if (prop.key.type === types_1.AST_NODE_TYPES.Literal) {
yield String(prop.key.value);
}
else {
yield prop.key.name;
}
}
else {
yield* extractClassListFromExpression(prop.key, context);
}
}
}
return;
}
const sourceCode = (0, compat_1.getSourceCode)(context);
const staticValue = (0, eslint_utils_1.getStaticValue)(node, sourceCode.scopeManager.globalScope);
if (staticValue) {
yield* extractClassListFromUnknown(staticValue.value);
return;
}
yield null;
}
function* extractClassListFromUnknown(value) {
if (!value) {
return;
}
if (Array.isArray(value)) {
for (const e of value) {
yield* extractClassListFromUnknown(e);
}
return;
}
if (typeof value === "object") {
yield* Object.keys(value);
return;
}
yield String(value);
}