eslint-plugin-vue-scoped-css
Version:
ESLint plugin for Scoped CSS in Vue.js
128 lines (127 loc) • 4.98 kB
JavaScript
;
const template_1 = require("../styles/template");
const context_1 = require("../styles/context");
const style_1 = require("../styles/context/style");
const compat_1 = require("../utils/compat");
module.exports = {
meta: {
docs: {
description: "disallow `@keyframes` which don't use in Scoped CSS",
categories: ["vue2-recommended", "vue3-recommended"],
default: "warn",
url: "https://future-architect.github.io/eslint-plugin-vue-scoped-css/rules/no-unused-keyframes.html",
},
fixable: null,
messages: {
unused: "The @keyframes `{{params}}` is unused.",
},
schema: [
{
type: "object",
properties: {
checkUnscoped: {
type: "boolean",
},
},
additionalProperties: false,
},
],
type: "suggestion",
},
create(context) {
var _a;
const checkUnscoped = Boolean((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.checkUnscoped);
const styles = (0, context_1.getStyleContexts)(context)
.filter(style_1.isValidStyleContext)
.filter((style) => style.scoped || checkUnscoped);
if (!styles.length) {
return {};
}
const reporter = (0, context_1.getCommentDirectivesReporter)(context);
const sourceCode = (0, compat_1.getSourceCode)(context);
function report(node) {
const paramsStartIndex = node.range[0] +
node.identifier.length +
node.name.length +
(node.node.raws.afterName || "").length;
const paramsEndIndex = paramsStartIndex + node.rawParamsText.length;
reporter.report({
node,
loc: {
start: sourceCode.getLocFromIndex(paramsStartIndex),
end: sourceCode.getLocFromIndex(paramsEndIndex),
},
messageId: "unused",
data: { params: node.paramsText },
});
}
function extract(style) {
const keyframes = [];
const animationNames = [];
const animations = [];
style.traverseNodes({
enterNode(node) {
if (node.type === "VCSSAtRule") {
if (/-?keyframes$/u.test(node.name) && node.identifier === "@") {
keyframes.push({
params: template_1.Template.ofParams(node),
node,
});
}
}
else if (node.type === "VCSSDeclarationProperty") {
if (/^(?:-\w+-)?animation-name$/u.test(node.property)) {
animationNames.push(node);
}
if (/^(?:-\w+-)?animation$/u.test(node.property)) {
animations.push(node);
}
}
},
});
return {
keyframes,
animationNames,
animations,
};
}
function verify(style) {
const { keyframes, animationNames, animations } = extract(style);
for (const decl of animationNames) {
for (const v of decl.value.split(",").map((s) => s.trim())) {
const value = template_1.Template.ofDeclValue(v, decl.lang);
for (let index = keyframes.length - 1; index >= 0; index--) {
const { params } = keyframes[index];
if (value.match(params)) {
keyframes.splice(index, 1);
}
}
}
}
for (const decl of animations) {
for (const v of decl.value.split(",").map((s) => s.trim())) {
const vals = v.trim().split(/\s+/u);
for (const val of vals) {
const value = template_1.Template.ofDeclValue(val, decl.lang);
for (let index = keyframes.length - 1; index >= 0; index--) {
const { params } = keyframes[index];
if (value.match(params)) {
keyframes.splice(index, 1);
}
}
}
}
}
for (const { node } of keyframes) {
report(node);
}
}
return {
"Program:exit"() {
for (const style of styles) {
verify(style);
}
},
};
},
};