UNPKG

eslint-plugin-better-tailwindcss

Version:

auto-wraps tailwind classes after a certain print width or class count into multiple lines to improve readability.

460 lines 16.9 kB
import { MatcherType } from "../types/rule.js"; import { findMatchingParentNodes, getLiteralNodesByMatchers, isCalleeMatchers, isCalleeName, isCalleeRegex, isInsideConditionalExpressionTest, isInsideLogicalExpressionLeft, isInsideMemberExpression, isTagMatchers, isTagName, isTagRegex, isVariableMatchers, isVariableName, isVariableRegex, matchesPathPattern } from "../utils/matchers.js"; import { getLiteralsByNodeAndRegex } from "../utils/regex.js"; import { createObjectPathElement, deduplicateLiterals, getContent, getIndentation, getQuotes, getWhitespace, 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, variables) { const literals = variables.reduce((literals, variable) => { if (!node.init) { return literals; } if (!isESVariableSymbol(node.id)) { return literals; } if (isVariableName(variable)) { if (!matchesName(variable, node.id.name)) { return literals; } literals.push(...getLiteralsByESExpression(ctx, [node.init])); } else if (isVariableRegex(variable)) { literals.push(...getLiteralsByESNodeAndRegex(ctx, node, variable)); } else if (isVariableMatchers(variable)) { if (!matchesName(variable[0], node.id.name)) { return literals; } if (isESArrowFunctionExpression(node.init) || isESCallExpression(node.init)) { return literals; } literals.push(...getLiteralsByESMatchers(ctx, node.init, variable[1])); } return literals; }, []); return deduplicateLiterals(literals); } export function getLiteralsByESCallExpression(ctx, node, callees) { const literals = callees.reduce((literals, callee) => { if (!isESCalleeSymbol(node.callee)) { return literals; } if (isCalleeName(callee)) { if (!matchesName(callee, node.callee.name)) { return literals; } literals.push(...getLiteralsByESExpression(ctx, node.arguments)); } else if (isCalleeRegex(callee)) { literals.push(...getLiteralsByESNodeAndRegex(ctx, node, callee)); } else if (isCalleeMatchers(callee)) { if (!matchesName(callee[0], node.callee.name)) { return literals; } literals.push(...getLiteralsByESMatchers(ctx, node, callee[1])); } return literals; }, []); return deduplicateLiterals(literals); } export function getLiteralsByTaggedTemplateExpression(ctx, node, tags) { const literals = tags.reduce((literals, tag) => { if (!isTaggedTemplateSymbol(node.tag)) { return literals; } if (isTagName(tag)) { if (tag !== node.tag.name) { return literals; } literals.push(...getLiteralsByESTemplateLiteral(ctx, node.quasi)); } else if (isTagRegex(tag)) { literals.push(...getLiteralsByESNodeAndRegex(ctx, node, tag)); } else if (isTagMatchers(tag)) { if (tag[0] !== node.tag.name) { return literals; } literals.push(...getLiteralsByESMatchers(ctx, node, tag[1])); } return literals; }, []); return deduplicateLiterals(literals); } 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 deduplicateLiterals(literals); } export function getLiteralsByESNodeAndRegex(ctx, node, regex) { if (!hasESNodeParentExtension(node)) { return []; } return getLiteralsByNodeAndRegex(ctx, node, regex, { getLiteralsByMatchingNode: (node) => { if (!isESNode(node)) { return; } if (isESSimpleStringLiteral(node)) { const literal = getStringLiteralByESStringLiteral(ctx, node); return literal ? [literal] : []; } if (isESTemplateElement(node) && hasESNodeParentExtension(node)) { const templateLiteralNode = findParentESTemplateLiteralByESTemplateElement(node); return templateLiteralNode && getLiteralsByESTemplateLiteral(ctx, templateLiteralNode); } }, getNodeByRangeStart: (start) => ctx.sourceCode.getNodeByRangeIndex(start), getNodeRange: node => isESNode(node) ? [node.range?.[0], node.range?.[1]] : undefined, getNodeSourceCode: node => isESNode(node) ? ctx.sourceCode.getText(node) : undefined }); } 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); return { ...quotes, ...whitespaces, ...multilineQuotes, content, indentation, 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 priorLiterals = findPriorLiterals(ctx, node); const content = getContent(raw, quotes, braces); const whitespaces = getWhitespace(content); const indentation = getIndentation(line); const multilineQuotes = getMultilineQuotes(node); return { ...whitespaces, ...quotes, ...braces, ...multilineQuotes, content, indentation, 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) => { if (node.type === "SpreadElement") { return acc; } 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 (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 (!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"; }])[0]; 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 (node.parent.type === "Property" && node.parent.parent.type === "ObjectExpression" && node.parent.value === node) { return true; } return isInsideObjectValue(node.parent); } 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"; } function isESCalleeSymbol(node) { return node.type === "Identifier" && !!node.parent && isESCallExpression(node.parent); } function isTaggedTemplateExpression(node) { return node.type === "TaggedTemplateExpression"; } function isTaggedTemplateSymbol(node) { return node.type === "Identifier" && !!node.parent && isTaggedTemplateExpression(node.parent); } export function isESVariableDeclarator(node) { return node.type === "VariableDeclarator"; } 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.startsWith("}") ? "}" : undefined; const openingBraces = raw.endsWith("${") ? "${" : undefined; return { closingBraces, openingBraces }; } function getESMatcherFunctions(matchers) { return matchers.reduce((matcherFunctions, matcher) => { switch (matcher.match) { case MatcherType.String: { matcherFunctions.push((node) => { if (!isESNode(node) || !hasESNodeParentExtension(node) || isInsideConditionalExpressionTest(node) || isInsideLogicalExpressionLeft(node) || isInsideMemberExpression(node) || isESObjectKey(node) || isInsideObjectValue(node)) { return false; } return isESStringLike(node); }); break; } case MatcherType.ObjectKey: { matcherFunctions.push((node) => { if (!isESNode(node) || !hasESNodeParentExtension(node) || !isESObjectKey(node) || isInsideConditionalExpressionTest(node) || isInsideLogicalExpressionLeft(node) || isInsideMemberExpression(node)) { return false; } const path = getESObjectPath(node); if (!path || !matcher.pathPattern) { return true; } return matchesPathPattern(path, matcher.pathPattern); }); break; } case MatcherType.ObjectValue: { matcherFunctions.push((node) => { if (!isESNode(node) || !hasESNodeParentExtension(node) || !isInsideObjectValue(node) || isInsideConditionalExpressionTest(node) || isInsideLogicalExpressionLeft(node) || isESObjectKey(node) || !isESStringLike(node)) { return false; } const path = getESObjectPath(node); if (!path || !matcher.pathPattern) { return true; } return matchesPathPattern(path, matcher.pathPattern); }); break; } } return matcherFunctions; }, []); } //# sourceMappingURL=es.js.map