eslint-plugin-better-tailwindcss
Version:
auto-wraps tailwind classes after a certain print width or class count into multiple lines to improve readability.
724 lines • 27.2 kB
JavaScript
import { MATCHER_RESULT, MatcherType } from "../types/rule.js";
import { getLiteralNodesByMatchers, isIndexedAccessLiteral, isInsideConditionalExpressionTest, isInsideDisallowedBinaryExpression, isInsideLogicalExpressionLeft, isInsideMemberExpression, matchesPathPattern } from "../utils/matchers.js";
import { createObjectPathElement, deduplicateLiterals, getContent, getIndentation, getQuotes, getWhitespace, isGenericNodeWithParent, matchesName } from "../utils/utils.js";
export const ES_CONTAINER_TYPES_TO_REPLACE_QUOTES = [
"ArrayExpression",
"Property",
"CallExpression",
"VariableDeclarator",
"ConditionalExpression",
"LogicalExpression"
];
export const ES_CONTAINER_TYPES_TO_INSERT_BRACES = [];
export function getLiteralsByESVariableDeclarator(ctx, node, selectors) {
const literals = selectors.reduce((literals, selector) => {
if (!node.init) {
return literals;
}
if (!isESVariableSymbol(node.id)) {
return literals;
}
if (!matchesName(selector.name, node.id.name)) {
return literals;
}
if (!selector.match) {
literals.push(...getLiteralsByESExpression(ctx, [node.init]));
return literals;
}
if (isESArrowFunctionExpression(node.init) || isESCallExpression(node.init) || isESFunctionExpression(node.init)) {
return literals;
}
literals.push(...getLiteralsByESMatchers(ctx, node.init, selector.match));
return literals;
}, []);
return literals.filter(deduplicateLiterals);
}
export function getLiteralsByESExportDefaultDeclaration(ctx, node, selectors) {
const literals = selectors.reduce((literals, selector) => {
if (!matchesName(selector.name, "default")) {
return literals;
}
if (!isESExportDefaultExpression(node.declaration)) {
return literals;
}
if (!selector.match) {
literals.push(...getLiteralsByESExpression(ctx, [node.declaration]));
return literals;
}
if (isESArrowFunctionExpression(node.declaration) || isESCallExpression(node.declaration) || isESFunctionExpression(node.declaration)) {
return literals;
}
literals.push(...getLiteralsByESMatchers(ctx, node.declaration, selector.match));
return literals;
}, []);
return literals.filter(deduplicateLiterals);
}
export function getLiteralsByESCallExpression(ctx, node, selectors) {
if (isNestedCurriedCall(node)) {
return [];
}
const callChain = getCurriedCallChain(node);
if (!callChain) {
return [];
}
const calleePath = getESCalleeName(callChain[0].callee, "path");
const calleeName = getESCalleeName(callChain[0].callee, "name");
const literals = selectors.reduce((literals, selector) => {
if (!selector.path && !selector.name ||
(!selector.path || !matchesName(selector.path, calleePath)) &&
(!selector.name || !matchesName(selector.name, calleeName))) {
return literals;
}
const targetCall = selector.targetCall ?? selector.callTarget;
const targetCalls = getTargetCalls(callChain, targetCall);
for (const targetCall of targetCalls) {
const targetArguments = getTargetArguments(targetCall.arguments, selector.targetArgument);
if (!selector.match) {
literals.push(...getLiteralsByESExpression(ctx, targetArguments));
continue;
}
for (const targetArgument of targetArguments) {
literals.push(...getLiteralsByESMatchers(ctx, targetArgument, selector.match));
}
}
return literals;
}, []);
return literals.filter(deduplicateLiterals);
}
export function getLiteralsByTaggedTemplateExpression(ctx, node, selectors) {
const tagPath = getTaggedTemplateName(node.tag, "path");
const tagName = getTaggedTemplateName(node.tag, "name");
if (!tagPath && !tagName) {
return [];
}
const literals = selectors.reduce((literals, selector) => {
if (!selector.path && !selector.name ||
(!selector.path || !matchesName(selector.path, tagPath)) &&
(!selector.name || !matchesName(selector.name, tagName))) {
return literals;
}
if (!selector.match) {
literals.push(...getLiteralsByESTemplateLiteral(ctx, node.quasi));
return literals;
}
literals.push(...getLiteralsByESMatchers(ctx, node, selector.match));
return literals;
}, []);
return literals.filter(deduplicateLiterals);
}
export function getLiteralsByESBareTemplateLiteral(ctx, node, selectors) {
const leadingComment = getLeadingComment(ctx, node);
if (isTaggedTemplateLiteral(node) || !leadingComment) {
return [];
}
const literals = selectors.reduce((literals, selector) => {
if (!selector.name || !matchesName(selector.name, leadingComment)) {
return literals;
}
if (!selector.match) {
literals.push(...getLiteralsByESTemplateLiteral(ctx, node));
return literals;
}
literals.push(...getLiteralsByESMatchers(ctx, node, selector.match));
return literals;
}, []);
return literals.filter(deduplicateLiterals);
}
export function getLiteralsByESLiteralNode(ctx, node) {
if (isESSimpleStringLiteral(node)) {
const literal = getStringLiteralByESStringLiteral(ctx, node);
return literal ? [literal] : [];
}
if (isESTemplateLiteral(node)) {
return getLiteralsByESTemplateLiteral(ctx, node);
}
if (isESTemplateElement(node) && hasESNodeParentExtension(node)) {
const literal = getLiteralByESTemplateElement(ctx, node);
return literal ? [literal] : [];
}
return [];
}
export function getLiteralsByESMatchers(ctx, node, matchers) {
const matcherFunctions = getESMatcherFunctions(matchers);
const literalNodes = getLiteralNodesByMatchers(ctx, node, matcherFunctions);
const literals = literalNodes.flatMap(literalNode => getLiteralsByESLiteralNode(ctx, literalNode));
return literals.filter(deduplicateLiterals);
}
export function getStringLiteralByESStringLiteral(ctx, node) {
const raw = node.raw;
if (!raw || !node.loc || !node.range || !node.parent.loc || !node.parent.range) {
return;
}
const line = ctx.sourceCode.lines[node.loc.start.line - 1];
const quotes = getQuotes(raw);
const priorLiterals = findPriorLiterals(ctx, node);
const content = getContent(raw, quotes);
const whitespaces = getWhitespace(content);
const indentation = getIndentation(line);
const multilineQuotes = getMultilineQuotes(node);
const supportsMultiline = !isESObjectKey(node);
const concatenation = getStringConcatenationMeta(node);
return {
...quotes,
...whitespaces,
...multilineQuotes,
...concatenation,
content,
indentation,
isInterpolated: false,
loc: node.loc,
priorLiterals,
range: node.range,
raw,
supportsMultiline,
type: "StringLiteral"
};
}
function getLiteralByESTemplateElement(ctx, node) {
const raw = ctx.sourceCode.getText(node);
if (!raw || !node.loc || !node.range || !node.parent.loc || !node.parent.range) {
return;
}
const line = ctx.sourceCode.lines[node.parent.loc.start.line - 1];
const quotes = getQuotes(raw);
const braces = getBracesByString(ctx, raw);
const isInterpolated = getIsInterpolated(ctx, raw);
const priorLiterals = findPriorLiterals(ctx, node);
const content = getContent(raw, quotes, braces);
const whitespaces = getWhitespace(content);
const indentation = getIndentation(line);
const multilineQuotes = getMultilineQuotes(node);
const concatenation = getStringConcatenationMeta(node);
return {
...whitespaces,
...quotes,
...braces,
...multilineQuotes,
...concatenation,
content,
indentation,
isInterpolated,
loc: node.loc,
priorLiterals,
range: node.range,
raw,
supportsMultiline: true,
type: "TemplateLiteral"
};
}
function getMultilineQuotes(node) {
const surroundingBraces = ES_CONTAINER_TYPES_TO_INSERT_BRACES.includes(node.parent.type);
const multilineQuotes = ES_CONTAINER_TYPES_TO_REPLACE_QUOTES.includes(node.parent.type)
? ["`"]
: [];
return {
multilineQuotes,
surroundingBraces
};
}
function getLiteralsByESExpression(ctx, args) {
return args.reduce((acc, node) => {
acc.push(...getLiteralsByESLiteralNode(ctx, node));
return acc;
}, []);
}
export function getLiteralsByESTemplateLiteral(ctx, node) {
return node.quasis.map(quasi => {
if (!hasESNodeParentExtension(quasi)) {
return;
}
return getLiteralByESTemplateElement(ctx, quasi);
}).filter((literal) => literal !== undefined);
}
export function findParentESTemplateLiteralByESTemplateElement(node) {
if (!hasESNodeParentExtension(node)) {
return;
}
if (node.parent.type === "TemplateLiteral") {
return node.parent;
}
return findParentESTemplateLiteralByESTemplateElement(node.parent);
}
function findPriorLiterals(ctx, node) {
if (!hasESNodeParentExtension(node)) {
return;
}
const priorLiterals = [];
let currentNode = node;
while (hasESNodeParentExtension(currentNode)) {
const parent = currentNode.parent;
if (isESCallExpression(parent)) {
break;
}
if (isESArrowFunctionExpression(parent)) {
break;
}
if (isESFunctionExpression(parent)) {
break;
}
if (isESVariableDeclarator(parent)) {
break;
}
if (parent.type === "TemplateLiteral") {
for (const quasi of parent.quasis) {
if (quasi.range === node.range) {
break;
}
if (quasi.type === "TemplateElement" && hasESNodeParentExtension(quasi)) {
const literal = getLiteralByESTemplateElement(ctx, quasi);
if (!literal) {
continue;
}
priorLiterals.push(literal);
}
}
}
if (parent.type === "TemplateElement") {
const literal = getLiteralByESTemplateElement(ctx, parent);
if (!literal) {
continue;
}
priorLiterals.push(literal);
}
if (parent.type === "Literal") {
const literal = getLiteralsByESLiteralNode(ctx, parent);
if (!literal) {
continue;
}
priorLiterals.push(...literal);
}
currentNode = parent;
}
return priorLiterals;
}
export function getESObjectPath(node) {
if (!isGenericNodeWithParent(node)) {
return;
}
if (!hasESNodeParentExtension(node)) {
return;
}
if (node.type !== "Property" &&
node.type !== "ObjectExpression" &&
node.type !== "ArrayExpression" &&
node.type !== "Identifier" &&
node.type !== "Literal" &&
node.type !== "TemplateElement") {
return;
}
const paths = [];
if (node.type === "Property") {
if (node.key.type === "Identifier") {
paths.unshift(createObjectPathElement(node.key.name));
}
else if (node.key.type === "Literal") {
paths.unshift(createObjectPathElement(node.key.value?.toString() ?? node.key.raw));
}
else {
return "";
}
}
if (isESStringLike(node) && isInsideObjectValue(node)) {
const property = findMatchingParentNodes(node, (node) => {
return isESNode(node) && node.type === "Property";
});
if (property) {
return getESObjectPath(property);
}
}
if (isESObjectKey(node)) {
const property = node.parent;
return getESObjectPath(property);
}
if (node.parent.type === "ArrayExpression" && node.type !== "Property" && node.type !== "TemplateElement") {
const index = node.parent.elements.indexOf(node);
paths.unshift(`[${index}]`);
}
paths.unshift(getESObjectPath(node.parent));
return paths.reduce((paths, currentPath) => {
if (!currentPath) {
return paths;
}
if (paths.length === 0) {
return [currentPath];
}
if (currentPath.startsWith("[") && currentPath.endsWith("]")) {
return [...paths, currentPath];
}
return [...paths, ".", currentPath];
}, []).join("");
}
export function isESObjectKey(node) {
return (node.parent.type === "Property" &&
node.parent.parent.type === "ObjectExpression" &&
node.parent.key === node);
}
export function isInsideObjectValue(node) {
if (!hasESNodeParentExtension(node)) {
return false;
}
// #34 allow call expressions as object values
if (isESCallExpression(node)) {
return false;
}
if (isESArrowFunctionExpression(node)) {
return false;
}
if (isESFunctionExpression(node)) {
return false;
}
if (node.parent.type === "Property" &&
node.parent.parent.type === "ObjectExpression" &&
node.parent.value === node) {
return true;
}
return isInsideObjectValue(node.parent);
}
function findMatchingParentNodes(node, matchesNode) {
if (!isGenericNodeWithParent(node)) {
return;
}
if (matchesNode(node.parent)) {
return node.parent;
}
return findMatchingParentNodes(node.parent, matchesNode);
}
export function isESSimpleStringLiteral(node) {
return (node.type === "Literal" &&
"value" in node &&
typeof node.value === "string");
}
export function isESStringLike(node) {
return isESSimpleStringLiteral(node) || isESTemplateElement(node);
}
export function isESTemplateLiteral(node) {
return node.type === "TemplateLiteral";
}
export function isESTemplateElement(node) {
return node.type === "TemplateElement";
}
export function isESNode(node) {
return (node !== null &&
typeof node === "object" &&
"type" in node);
}
export function isESCallExpression(node) {
return node.type === "CallExpression";
}
export function isESArrowFunctionExpression(node) {
return node.type === "ArrowFunctionExpression";
}
export function isESFunctionExpression(node) {
return node.type === "FunctionExpression";
}
export function isESFunctionDeclaration(node) {
return node.type === "FunctionDeclaration";
}
export function isESAnonymousFunction(node) {
if (isESArrowFunctionExpression(node)) {
return true;
}
if (isESFunctionExpression(node) && node.id === null) {
return true;
}
return false;
}
export function isESArrowFunctionWithoutBody(node) {
return isESArrowFunctionExpression(node) && node.body.type !== "BlockStatement";
}
export function isESReturnStatement(node) {
return node.type === "ReturnStatement";
}
function getESMemberExpressionPropertyName(node) {
if (!node.computed && node.property.type === "Identifier") {
return node.property.name;
}
if (node.computed && isESSimpleStringLiteral(node.property)) {
return node.property.value;
}
}
function getESCalleeName(node, type) {
if (node.type === "Identifier" && "name" in node && typeof node.name === "string") {
return node.name;
}
if (node.type === "MemberExpression" && "object" in node) {
const memberNode = node;
if (memberNode.object.type === "Super") {
return;
}
const object = getESCalleeName(memberNode.object, type);
const property = getESMemberExpressionPropertyName(memberNode);
if (!property) {
return;
}
if (type === "name") {
return property;
}
if (!object) {
return;
}
return `${object}.${property}`;
}
if (node.type === "ChainExpression" && "expression" in node) {
return getESCalleeName(node.expression, type);
}
}
function getTaggedTemplateName(node, type) {
if (node.type === "Identifier" && "name" in node && typeof node.name === "string" &&
hasESNodeParentExtension(node) &&
isTaggedTemplateExpression(node.parent)) {
return node.name;
}
if (node.type === "MemberExpression") {
return getESCalleeName(node, type);
}
if (node.type === "CallExpression") {
return getESCalleeName(node.callee, type);
}
if (node.type === "ChainExpression" && "expression" in node) {
return getTaggedTemplateName(node.expression, type);
}
}
function isNestedCurriedCall(node) {
return hasESNodeParentExtension(node) && isESCallExpression(node.parent) && node.parent.callee === node;
}
function getCurriedCallChain(node) {
const calls = [node];
let currentCall = node;
while (isESCallExpression(currentCall.callee)) {
currentCall = currentCall.callee;
calls.unshift(currentCall);
}
return calls;
}
function getTargetCalls(callChain, callTarget) {
return getTargetItems(callChain, callTarget, "first");
}
function getTargetArguments(args, argumentTarget) {
const expressionArgs = args.map((arg) => {
return arg.type === "SpreadElement"
? arg.argument
: arg;
});
if (typeof argumentTarget !== "number") {
return getTargetItems(expressionArgs, argumentTarget, "all");
}
if (args.length === 0) {
return [];
}
const index = argumentTarget >= 0
? argumentTarget
: args.length + argumentTarget;
if (index < 0 || index >= args.length) {
return [];
}
const targetArg = args[index];
return [targetArg.type === "SpreadElement" ? targetArg.argument : targetArg];
}
function getTargetItems(items, target, defaultTarget) {
if (items.length === 0) {
return [];
}
if (target === "all" || target === undefined && defaultTarget === "all") {
return items;
}
if (target === "last") {
return [items[items.length - 1]];
}
if (target === undefined || target === "first") {
return [items[0]];
}
const index = target >= 0
? target
: items.length + target;
if (index < 0 || index >= items.length) {
return [];
}
return [items[index]];
}
function isTaggedTemplateExpression(node) {
return node.type === "TaggedTemplateExpression";
}
function isTaggedTemplateLiteral(node) {
return hasESNodeParentExtension(node) && isTaggedTemplateExpression(node.parent);
}
export function isESVariableDeclarator(node) {
return node.type === "VariableDeclarator";
}
function isESExportDefaultExpression(node) {
if (node.type === "FunctionDeclaration") {
return false;
}
if (node.type === "ClassDeclaration") {
return false;
}
return true;
}
function isESVariableSymbol(node) {
return node.type === "Identifier" && !!node.parent && isESVariableDeclarator(node.parent);
}
export function hasESNodeParentExtension(node) {
return "parent" in node && !!node.parent;
}
function getBracesByString(ctx, raw) {
const closingBraces = raw.trim().startsWith("}") ? "}" : undefined;
const openingBraces = raw.trim().endsWith("${") ? "${" : undefined;
return {
closingBraces,
openingBraces
};
}
function getIsInterpolated(ctx, raw) {
const braces = getBracesByString(ctx, raw);
return !!braces.closingBraces || !!braces.openingBraces;
}
function getLeadingComment(ctx, node) {
try {
const token = ctx.sourceCode.getTokenBefore(node, { includeComments: true });
if (token && isESTokenComment(token)) {
return token.value.trim();
}
}
catch { }
}
function isESTokenComment(token) {
return (token.type === "Block" || token.type === "Line") && typeof token.value === "string";
}
function getStringConcatenationMeta(node, isConcatenatedLeft = false, isConcatenatedRight = false) {
if (!hasESNodeParentExtension(node)) {
return {
isConcatenatedLeft,
isConcatenatedRight
};
}
const parent = node.parent;
if (parent.type === "BinaryExpression" && parent.operator === "+") {
return getStringConcatenationMeta(parent, isConcatenatedLeft || parent.right === node, isConcatenatedRight || parent.left === node);
}
return getStringConcatenationMeta(parent, isConcatenatedLeft, isConcatenatedRight);
}
export function getESMatcherFunctions(matchers, options) {
return matchers.reduce((matcherFunctions, matcher) => {
switch (matcher.type) {
case MatcherType.AnonymousFunctionReturn: {
matcherFunctions.push(node => {
if (isESNode(node) && (isESCallExpression(node) ||
isESVariableDeclarator(node))) {
return MATCHER_RESULT.UNCROSSABLE_BOUNDARY;
}
if (!isESNode(node) ||
!hasESNodeParentExtension(node) ||
!isESAnonymousFunction(node)) {
return MATCHER_RESULT.NO_MATCH;
}
// return matchers directly if the arrow function immediately returns
if (isESArrowFunctionWithoutBody(node)) {
return [(node) => {
if (!isESNode(node) ||
!hasESNodeParentExtension(node) ||
!isESArrowFunctionWithoutBody(node.parent) ||
node !== node.parent.body) {
return MATCHER_RESULT.NO_MATCH;
}
return getESMatcherFunctions(matcher.match, options);
}];
}
// create a matcher function that first matches the return statement and then the final matchers
return [(node) => {
if (isESNode(node) && (isESCallExpression(node) ||
isESArrowFunctionExpression(node) ||
isESVariableDeclarator(node) ||
isESFunctionExpression(node) ||
isESFunctionDeclaration(node))) {
return MATCHER_RESULT.UNCROSSABLE_BOUNDARY;
}
if (!isESNode(node) ||
!hasESNodeParentExtension(node) ||
!isESReturnStatement(node)) {
return MATCHER_RESULT.NO_MATCH;
}
return getESMatcherFunctions(matcher.match, options);
}];
});
break;
}
case MatcherType.String: {
matcherFunctions.push(node => {
if (isESNode(node) && (isESCallExpression(node) ||
isESArrowFunctionExpression(node) ||
isESVariableDeclarator(node) ||
isESFunctionExpression(node))) {
return MATCHER_RESULT.UNCROSSABLE_BOUNDARY;
}
if (!isESNode(node) ||
!hasESNodeParentExtension(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isIndexedAccessLiteral(node) ||
isESObjectKey(node) ||
isInsideObjectValue(node)) {
return MATCHER_RESULT.NO_MATCH;
}
return isESStringLike(node) || !!options?.isStringLikeNode?.(node);
});
break;
}
case MatcherType.ObjectKey: {
matcherFunctions.push(node => {
if (isESNode(node) && (isESCallExpression(node) ||
isESArrowFunctionExpression(node) ||
isESVariableDeclarator(node) ||
isESFunctionExpression(node))) {
return MATCHER_RESULT.UNCROSSABLE_BOUNDARY;
}
if (!isESNode(node) ||
!hasESNodeParentExtension(node) ||
!isESObjectKey(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isInsideMemberExpression(node) ||
isIndexedAccessLiteral(node)) {
return MATCHER_RESULT.NO_MATCH;
}
const path = getESObjectPath(node);
if (!path || !matcher.path) {
return MATCHER_RESULT.MATCH;
}
return matchesPathPattern(path, matcher.path);
});
break;
}
case MatcherType.ObjectValue: {
matcherFunctions.push(node => {
if (isESNode(node) && (isESCallExpression(node) ||
isESArrowFunctionExpression(node) ||
isESVariableDeclarator(node) ||
isESFunctionExpression(node))) {
return MATCHER_RESULT.UNCROSSABLE_BOUNDARY;
}
if (!isESNode(node) ||
!hasESNodeParentExtension(node) ||
!isInsideObjectValue(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isESObjectKey(node) ||
isIndexedAccessLiteral(node) ||
!isESStringLike(node) && !options?.isStringLikeNode?.(node)) {
return MATCHER_RESULT.NO_MATCH;
}
const path = getESObjectPath(node);
if (!path || !matcher.path) {
return MATCHER_RESULT.MATCH;
}
return matchesPathPattern(path, matcher.path);
});
break;
}
}
return matcherFunctions;
}, []);
}
//# sourceMappingURL=es.js.map