UNPKG

eslint-plugin-readable-tailwind

Version:

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

154 lines 6.2 kB
import { DEFAULT_ATTRIBUTE_NAMES, DEFAULT_CALLEE_NAMES, DEFAULT_TAG_NAMES, DEFAULT_VARIABLE_NAMES } from "../options/default-options.js"; import { ATTRIBUTE_SCHEMA, CALLEE_SCHEMA, TAG_SCHEMA, VARIABLE_SCHEMA } from "../options/descriptions.js"; import { hasESNodeParentExtension, isESCallExpression, isESVariableDeclarator } from "../parsers/es.js"; import { escapeNestedQuotes } from "../utils/quotes.js"; import { createRuleListener } from "../utils/rule.js"; import { getCommonOptions, splitClasses, splitWhitespaces } from "../utils/utils.js"; const defaultOptions = { attributes: DEFAULT_ATTRIBUTE_NAMES, callees: DEFAULT_CALLEE_NAMES, tags: DEFAULT_TAG_NAMES, variables: DEFAULT_VARIABLE_NAMES }; export const tailwindNoDuplicateClasses = { name: "no-duplicate-classes", rule: { create: ctx => createRuleListener(ctx, getOptions(ctx), lintLiterals), meta: { docs: { category: "Stylistic Issues", description: "Disallow duplicate class names in tailwind classes.", recommended: true, url: "https://github.com/schoero/eslint-plugin-readable-tailwind/blob/main/docs/rules/no-duplicate-classes.md" }, fixable: "code", schema: [ { additionalProperties: false, properties: { ...CALLEE_SCHEMA, ...ATTRIBUTE_SCHEMA, ...VARIABLE_SCHEMA, ...TAG_SCHEMA }, type: "object" } ], type: "layout" } } }; function lintLiterals(ctx, literals) { for (const literal of literals) { const esNode = ctx.sourceCode.getNodeByRangeIndex(literal.range[0]); const parentLiteralNodes = esNode && findParentLiteralNodes(esNode); const parentLiterals = parentLiteralNodes && getLiteralsFromParentLiteralNodes(parentLiteralNodes, literals); const parentClasses = parentLiterals ? getClassesFromLiteralNodes(parentLiterals) : []; const duplicates = []; const classes = literal.content; const classChunks = splitClasses(classes); const whitespaceChunks = splitWhitespaces(classes); const finalChunks = []; const startsWithWhitespace = whitespaceChunks.length > 0 && whitespaceChunks[0] !== ""; const endsWithWhitespace = whitespaceChunks.length > 0 && whitespaceChunks[whitespaceChunks.length - 1] !== ""; for (let i = 0; i < whitespaceChunks.length; i++) { if (whitespaceChunks[i]) { finalChunks.push(whitespaceChunks[i]); } if (classChunks[i]) { // always push sticky classes without adding to the register if (!startsWithWhitespace && i === 0 && literal.closingBraces || !endsWithWhitespace && i === classChunks.length - 1 && literal.openingBraces) { finalChunks.push(classChunks[i]); continue; } if (parentClasses.includes(classChunks[i])) { if (!duplicates.includes(classChunks[i])) { duplicates.push(classChunks[i]); } } else { finalChunks.push(classChunks[i]); parentClasses.push(classChunks[i]); } } } const escapedClasses = escapeNestedQuotes(finalChunks.join(""), literal.openingQuote ?? "\""); const fixedClasses = [ literal.openingQuote ?? "", literal.type === "TemplateLiteral" && literal.closingBraces ? literal.closingBraces : "", escapedClasses, literal.type === "TemplateLiteral" && literal.openingBraces ? literal.openingBraces : "", literal.closingQuote ?? "" ].join(""); if (literal.raw === fixedClasses) { continue; } ctx.report({ data: { duplicateClassname: duplicates.join(", ") }, fix(fixer) { return fixer.replaceTextRange(literal.range, fixedClasses); }, loc: literal.loc, message: "Duplicate classname: \"{{ duplicateClassname }}\"." }); } } function findParentLiteralNodes(node) { if (!hasESNodeParentExtension(node)) { return; } const parentLiterals = []; let currentNode = node; while (hasESNodeParentExtension(currentNode)) { const parent = currentNode.parent; if (isESCallExpression(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") { parentLiterals.push(quasi); } } } if (parent.type === "TemplateElement" || parent.type === "Literal") { parentLiterals.push(parent); } currentNode = parent; } return parentLiterals; } function getLiteralsFromParentLiteralNodes(parentLiteralNodes, literals) { return parentLiteralNodes.map(parentLiteralNode => { return literals.find(literal => literal.range === parentLiteralNode.range); }); } function getClassesFromLiteralNodes(literals) { return literals.reduce((combinedClasses, literal) => { if (!literal) { return combinedClasses; } const classes = literal.content; const split = splitClasses(classes); for (const className of split) { if (!combinedClasses.includes(className)) { combinedClasses.push(className); } } return combinedClasses; }, []); } function getOptions(ctx) { return getCommonOptions(ctx); } //# sourceMappingURL=tailwind-no-duplicate-classes.js.map