UNPKG

eslint-plugin-readable-tailwind

Version:

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

118 lines 4.88 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 { escapeNestedQuotes } from "../utils/quotes.js"; import { createRuleListener } from "../utils/rule.js"; import { getCommonOptions, getExactClassLocation, splitClasses, splitWhitespaces } from "../utils/utils.js"; const defaultOptions = { attributes: DEFAULT_ATTRIBUTE_NAMES, callees: DEFAULT_CALLEE_NAMES, tags: DEFAULT_TAG_NAMES, variables: DEFAULT_VARIABLE_NAMES }; const DOCUMENTATION_URL = "https://github.com/schoero/eslint-plugin-better-tailwindcss/blob/main/docs/rules/no-duplicate-classes.md"; export const noDuplicateClasses = { 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: DOCUMENTATION_URL }, 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 parentClasses = literal.priorLiterals ? getClassesFromLiteralNodes(literal.priorLiterals) : []; 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 ?? literal.closingQuote ?? "\""); 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; } for (const className of duplicates) { ctx.report({ data: { duplicateClassname: className }, fix(fixer) { return fixer.replaceTextRange(literal.range, fixedClasses); }, loc: getExactClassLocation(literal, className, false, true), message: "Duplicate classname: \"{{ duplicateClassname }}\"." }); } } } 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=no-duplicate-classes.js.map