eslint-plugin-vue-scoped-css
Version:
ESLint plugin for Scoped CSS in Vue.js
649 lines (648 loc) • 22.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryContext = void 0;
exports.createQueryContext = createQueryContext;
const selectors_1 = require("../../utils/selectors");
const elements_1 = require("./elements");
const attribute_tracker_1 = require("./attribute-tracker");
const context_1 = require("../../context");
const nodes_1 = require("../../utils/nodes");
const template_1 = require("../../template");
const templates_1 = require("../../../utils/templates");
const style_1 = require("../../context/style");
const reference_expression_1 = require("./reference-expression");
const compat_1 = require("../../../utils/compat");
const TRANSITION_CLASS_BASES = [
"enter",
"enter-from",
"enter-active",
"enter-to",
"leave",
"leave-from",
"leave-active",
"leave-to",
];
const TRANSITION_GROUP_CLASS_BASES = [...TRANSITION_CLASS_BASES, "move"];
class QueryContext {
constructor(document) {
this.elements = [];
this.document = document || this;
}
queryStep(selectorNode) {
return new ElementsQueryContext(queryStep(this.elements, selectorNode, this.document), this.document);
}
reverseQueryStep(selectorNode) {
return new ElementsQueryContext(reverseQueryStep(this.elements, selectorNode, this.document), this.document);
}
filter(predicate) {
return new ElementsQueryContext(this.elements.filter(predicate), this.document);
}
split() {
return this.elements.map((e) => new ElementsQueryContext([e], this.document));
}
}
exports.QueryContext = QueryContext;
class VueDocumentQueryContext extends QueryContext {
constructor(context, options) {
super();
const sourceCode = (0, compat_1.getSourceCode)(context);
const { ast } = sourceCode;
this.elements = ast.templateBody
? [...genDescendantElements([ast.templateBody])]
: [];
this.context = context;
this.options = options;
if (options.captureClassesFromDoc.length > 0) {
this.docsModifiers = (0, context_1.getStyleContexts)(context)
.filter(style_1.isValidStyleContext)
.filter((style) => style.scoped)
.map((style) => extractClassesFromDoc(style, options.captureClassesFromDoc))
.reduce((r, a) => r.concat(a), []);
}
else {
this.docsModifiers = [];
}
}
}
function extractClassesFromDoc(style, captureClassesFromDoc) {
const results = new Set();
for (const comment of style.cssNode.comments) {
for (const regexp of captureClassesFromDoc) {
regexp.lastIndex = 0;
let re;
while ((re = regexp.exec(comment.text))) {
if (re.length > 1) {
for (const s of re.slice(1)) {
results.add(s);
}
}
else {
results.add(re[0]);
}
}
}
}
return [...results];
}
class ElementsQueryContext extends QueryContext {
constructor(elements, document) {
super(document);
this.elements = [...elements];
}
}
function createQueryContext(context, options) {
return new VueDocumentQueryContext(context, options);
}
function* queryStep(elements, selectorNode, document) {
if ((0, selectors_1.isSelectorCombinator)(selectorNode)) {
if ((0, selectors_1.isChildCombinator)(selectorNode)) {
yield* genChildElements(elements);
return;
}
else if ((0, selectors_1.isDescendantCombinator)(selectorNode) ||
(0, selectors_1.isDeepCombinator)(selectorNode)) {
yield* genDescendantElements(elements);
return;
}
else if ((0, selectors_1.isAdjacentSiblingCombinator)(selectorNode)) {
yield* genAdjacentSiblingElements(elements);
return;
}
else if ((0, selectors_1.isGeneralSiblingCombinator)(selectorNode)) {
yield* genGeneralSiblingElements(elements);
return;
}
}
else if ((0, selectors_1.isVDeepPseudo)(selectorNode)) {
yield* genVDeepElements(elements, (0, selectors_1.normalizePseudoParams)(selectorNode, selectorNode.nodes), query);
return;
}
else if ((0, selectors_1.isVSlottedPseudo)(selectorNode)) {
yield* genVSlottedElements(elements, (0, selectors_1.normalizePseudoParams)(selectorNode, selectorNode.nodes), query);
return;
}
else if ((0, selectors_1.isVGlobalPseudo)(selectorNode)) {
yield* genVGlobalElements(elements, (0, selectors_1.normalizePseudoParams)(selectorNode, selectorNode.nodes), document, query);
return;
}
if ((0, selectors_1.isTypeSelector)(selectorNode)) {
yield* genElementsByTagName(elements, template_1.Template.ofSelector(selectorNode));
return;
}
else if ((0, selectors_1.isIDSelector)(selectorNode)) {
yield* genElementsById(elements, template_1.Template.ofSelector(selectorNode), document);
return;
}
else if ((0, selectors_1.isClassSelector)(selectorNode)) {
yield* genElementsByClassName(elements, template_1.Template.ofSelector(selectorNode), document);
return;
}
else if ((0, selectors_1.isUniversalSelector)(selectorNode)) {
yield* elements;
return;
}
yield* elements;
function query(els, selList) {
return selList.reduce((res, sel) => [
...queryStep(res, sel, document),
], els);
}
}
function* reverseQueryStep(elements, selectorNode, document) {
if ((0, selectors_1.isSelectorCombinator)(selectorNode)) {
if ((0, selectors_1.isChildCombinator)(selectorNode)) {
yield* genParentElements(elements);
return;
}
else if ((0, selectors_1.isDescendantCombinator)(selectorNode) ||
(0, selectors_1.isDeepCombinator)(selectorNode)) {
yield* genAncestorElements(elements);
return;
}
else if ((0, selectors_1.isAdjacentSiblingCombinator)(selectorNode)) {
yield* genPrevAdjacentSiblingElements(elements);
return;
}
else if ((0, selectors_1.isGeneralSiblingCombinator)(selectorNode)) {
yield* genPrevGeneralSiblingElements(elements);
return;
}
}
else if ((0, selectors_1.isVDeepPseudo)(selectorNode)) {
yield* genVDeepElements(elements, (0, selectors_1.normalizePseudoParams)(selectorNode, selectorNode.nodes), query);
return;
}
else if ((0, selectors_1.isVSlottedPseudo)(selectorNode)) {
yield* genVSlottedElements(elements, (0, selectors_1.normalizePseudoParams)(selectorNode, selectorNode.nodes), query);
return;
}
else if ((0, selectors_1.isVGlobalPseudo)(selectorNode)) {
yield* genVGlobalElements(elements, (0, selectors_1.normalizePseudoParams)(selectorNode, selectorNode.nodes), document, query);
return;
}
yield* queryStep(elements, selectorNode, document);
function query(els, selList) {
return selList.reduceRight((res, sel) => [
...reverseQueryStep(res, sel, document),
], els);
}
}
function* genDescendantElements(elements) {
const found = new Set();
for (const e of genChildElements(elements)) {
if (!found.has(e)) {
yield e;
found.add(e);
for (const p of genDescendantElements([e])) {
if (!found.has(p)) {
yield p;
found.add(p);
}
}
}
}
}
function* genAncestorElements(elements) {
const found = new Set();
for (const e of genParentElements(elements)) {
yield e;
found.add(e);
for (const a of genAncestorElements([e])) {
if (!found.has(a)) {
yield a;
found.add(a);
}
}
}
}
function* genChildElements(elements) {
function* genChildren(elm) {
for (const e of elm.children.filter(templates_1.isVElement)) {
if ((0, elements_1.isSkipElement)(e)) {
yield* genChildren(e);
}
else if ((0, elements_1.isSlotElement)(e)) {
yield e;
yield* genChildren(e);
}
else {
yield e;
}
}
}
for (const element of elements) {
if ((0, elements_1.isSlotElement)(element)) {
continue;
}
yield* genChildren(element);
}
}
function genParentElements(elements) {
return iterateUnique(function* () {
for (const element of elements) {
yield (0, elements_1.getParentElement)(element);
}
});
}
function genAdjacentSiblingElements(elements) {
return iterateUnique(function* () {
for (const element of elements) {
if (hasVFor(element)) {
yield element;
}
const vForTemplate = getVForTemplate(element);
if (vForTemplate) {
const children = [...genChildElements([vForTemplate])];
const index = children.indexOf(element);
yield children[index + 1] || children[0];
}
const parent = (0, elements_1.getParentElement)(element);
if (parent) {
const children = [...genChildElements([parent])];
const index = children.indexOf(element);
yield children[index + 1];
}
}
});
}
function genPrevAdjacentSiblingElements(elements) {
return iterateUnique(function* () {
for (const element of elements) {
if (hasVFor(element)) {
yield element;
}
const vForTemplate = getVForTemplate(element);
if (vForTemplate) {
const children = [...genChildElements([vForTemplate])];
const index = children.indexOf(element);
yield children[index - 1] || children[children.length - 1];
}
const parent = (0, elements_1.getParentElement)(element);
if (parent) {
const children = [...genChildElements([parent])];
const index = children.indexOf(element);
yield children[index - 1];
}
}
});
}
function genGeneralSiblingElements(elements) {
return iterateUnique(function* () {
for (const element of elements) {
if (hasVFor(element)) {
yield element;
}
const vForTemplate = getVForTemplate(element);
if (vForTemplate) {
yield* genChildElements([vForTemplate]);
}
const parent = (0, elements_1.getParentElement)(element);
if (parent) {
const children = [...genChildElements([parent])];
const index = children.indexOf(element);
yield* children.slice(index + 1);
}
}
});
}
function genPrevGeneralSiblingElements(elements) {
return iterateUnique(function* () {
for (const element of elements) {
if (hasVFor(element)) {
yield element;
}
const vForTemplate = getVForTemplate(element);
if (vForTemplate) {
yield* genChildElements([vForTemplate]);
}
const parent = (0, elements_1.getParentElement)(element);
if (parent) {
const children = [...genChildElements([parent])];
const index = children.indexOf(element);
yield* children.slice(0, index);
}
}
});
}
function* genVDeepElements(elements, params, query) {
if (params.length) {
yield* iterateUnique(function* () {
for (const node of params) {
yield* query(elements, node.nodes);
}
});
}
else {
yield* elements;
}
}
function genVSlottedElements(elements, params, query) {
return iterateUnique(function* () {
for (const element of elements) {
if ((0, elements_1.isSlotElement)(element)) {
yield element;
}
}
for (const node of params) {
const els = query(elements, node.nodes);
for (const e of els) {
if (inSlot(e)) {
yield e;
}
}
}
});
function inSlot(e) {
if ((0, elements_1.isSlotElement)(e)) {
return true;
}
return Boolean(e && e.parent && inSlot(e.parent));
}
}
function genVGlobalElements(_elements, params, document, query) {
return iterateUnique(function* () {
for (const node of params) {
yield* query(document.elements, node.nodes);
}
});
}
function* genElementsByTagName(elements, tagName) {
for (const element of elements) {
if (element.name === "component" || element.name === "slot") {
yield element;
}
else if (tagName.toLowerCase().matchString(element.name)) {
yield element;
}
}
}
function* genElementsById(elements, id, document) {
for (const element of elements) {
if (matchId(element, id, document)) {
yield element;
}
}
}
function* genElementsByClassName(elements, className, document) {
let removeModifierClassName = null;
if (document.options.ignoreBEMModifier) {
if (className.hasString("--")) {
const list = className.divide("--");
list.pop();
if (list.length) {
removeModifierClassName = list.reduce((r, a) => r.concat(a));
}
}
}
for (const docMod of document.docsModifiers) {
if (docMod.startsWith(":")) {
continue;
}
const modClassName = docMod.startsWith(".")
? docMod.slice(1)
: docMod;
if (className.matchString(modClassName)) {
yield* elements;
return;
}
else if (removeModifierClassName) {
if (removeModifierClassName.matchString(modClassName)) {
yield* elements;
return;
}
}
}
for (const element of elements) {
if (matchClassName(element, className, document)) {
yield element;
}
else if (removeModifierClassName &&
matchClassName(element, removeModifierClassName, document)) {
yield element;
}
}
}
function matchId(element, id, document) {
const nodes = (0, attribute_tracker_1.getAttributeValueNodes)(element, "id", document.context);
if (nodes == null) {
return true;
}
for (const node of nodes) {
const value = template_1.Template.ofNode(node);
if (value == null) {
return true;
}
if (value.match(id)) {
return true;
}
}
return false;
}
function matchClassName(element, className, document) {
if ((0, elements_1.isElementWrappedInTransition)(element)) {
const transition = (0, elements_1.getWrapperTransition)(element);
if (transition != null &&
matchTransitionClassName(transition, className, document)) {
return true;
}
}
const nodes = (0, attribute_tracker_1.getAttributeValueNodes)(element, "class", document.context);
if (nodes == null) {
return true;
}
for (const node of nodes) {
if (node.type === "VLiteral") {
if (includesClassName(node.value, className)) {
return true;
}
}
else if (matchClassNameExpression(node, className, document)) {
return true;
}
}
const refNames = getRefNames(element, document);
const vueComponent = (0, context_1.getVueComponentContext)(document.context);
if (vueComponent &&
vueComponent
.getClassesOperatedByClassList(refNames, (0, elements_1.isRootElement)(element))
.filter(((n) => n.type === "Literal"))
.some((n) => matchClassNameExpression(n, className, document))) {
return true;
}
return false;
}
function getRefNames(element, document) {
const refNameNodes = (0, attribute_tracker_1.getAttributeValueNodes)(element, "ref", document.context);
if (refNameNodes == null) {
return null;
}
const refNames = [];
for (const refNameNode of refNameNodes) {
const refName = template_1.Template.ofNode(refNameNode);
if (refName == null) {
return null;
}
refNames.push(refName);
}
return refNames;
}
function matchTransitionClassName(element, className, document) {
const classBases = (0, templates_1.isTransitionElement)(element)
? TRANSITION_CLASS_BASES
: TRANSITION_GROUP_CLASS_BASES;
const nameNodes = (0, attribute_tracker_1.getAttributeValueNodes)(element, "name", document.context);
for (const classBase of classBases) {
const classNameNodes = (0, attribute_tracker_1.getAttributeValueNodes)(element, `${classBase}-class`, document.context);
if (classNameNodes == null) {
return true;
}
if (classNameNodes.length) {
for (const classNameNode of classNameNodes) {
const value = template_1.Template.ofNode(classNameNode);
if (value == null) {
return true;
}
if (value.match(className)) {
return true;
}
}
}
else if (nameNodes == null) {
if (className.endsWith(`-${classBase}`)) {
return true;
}
}
else if (nameNodes.length === 0) {
if (className.matchString(`v-${classBase}`)) {
return true;
}
}
else {
for (const nameNode of nameNodes) {
const name = template_1.Template.ofNode(nameNode);
if (name == null) {
return true;
}
if (className.match(name.concat(`-${classBase}`))) {
return true;
}
}
}
}
return false;
}
function matchClassNameExpression(expression, className, document) {
const literal = template_1.Template.ofNode(expression);
if (literal != null) {
if (includesClassName(literal, className)) {
return true;
}
}
else if (expression.type === "Identifier") {
const string = (0, nodes_1.getStringFromNode)(expression, document.context);
if (string == null) {
return true;
}
if (includesClassName(string, className)) {
return true;
}
}
else if (expression.type === "ArrayExpression") {
if (matchClassNameForArrayExpression(expression, className, document)) {
return true;
}
}
else if (expression.type === "ObjectExpression") {
if (matchClassNameForObjectExpression(expression, className, document)) {
return true;
}
}
else {
return true;
}
return false;
}
function matchClassNameForArrayExpression(expression, className, document) {
for (const e of expression.elements) {
if (e.type === "SpreadElement") {
if (matchClassNameExpression(e.argument, className, document)) {
return true;
}
}
else {
const expressions = (0, reference_expression_1.getReferenceExpressions)(e, document.context);
if (expressions) {
for (const e2 of expressions) {
if (matchClassNameExpression(e2, className, document)) {
return true;
}
}
}
}
}
return false;
}
function matchClassNameForObjectExpression(expression, className, document) {
for (const prop of expression.properties) {
if (prop.type !== "Property") {
return true;
}
if (prop.computed) {
if (prop.key.type === "Identifier" ||
prop.key.type === "Literal" ||
prop.key.type === "TemplateLiteral" ||
prop.key.type === "BinaryExpression") {
if (matchClassNameExpression(prop.key, className, document)) {
return true;
}
}
else {
return true;
}
}
else {
if (prop.key.type === "Identifier") {
if (includesClassName(prop.key.name, className)) {
return true;
}
}
else if (prop.key.type === "Literal") {
if (includesClassName(`${prop.key.value}`, className)) {
return true;
}
}
}
}
return false;
}
function includesClassName(value, className) {
if (typeof value === "string") {
return value.split(/\s+/u).some((s) => className.matchString(s));
}
return value.divide(/\s+/u).some((s) => className.match(s));
}
function* iterateUnique(gen) {
const found = new Set();
for (const e of gen()) {
if (e != null && !found.has(e)) {
yield e;
found.add(e);
}
}
}
function hasVFor(element) {
return element.startTag.attributes.some((attr) => attr.directive && attr.key.name.name === "for");
}
function getVForTemplate(element) {
let parent = element.parent;
while (parent) {
if (parent.type !== "VElement" || parent.name !== "template") {
return null;
}
if (hasVFor(parent)) {
return parent;
}
parent = parent.parent;
}
return null;
}