From c56ec3b88baa54d820db1842fbd68e863b6a597c Mon Sep 17 00:00:00 2001 From: Ben Bachem <10088265+bezbac@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:29:14 +0200 Subject: [PATCH] feat: Support enforcing `truncate` shorthand --- lib/rules/enforces-shorthand.js | 49 +++++++++++++++- tests/lib/rules/enforces-shorthand.js | 84 +++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/lib/rules/enforces-shorthand.js b/lib/rules/enforces-shorthand.js index b10e1348..9e40ba3b 100644 --- a/lib/rules/enforces-shorthand.js +++ b/lib/rules/enforces-shorthand.js @@ -73,6 +73,11 @@ module.exports = { // Helpers //---------------------------------------------------------------------- + // These are shorthand candidates that do not share the same parent type + const complexEquivalences = [ + [["overflow-hidden", "text-ellipsis", "whitespace-nowrap"], "truncate"] + ] + // Init assets const targetProperties = { Layout: ['Overflow', 'Overscroll Behavior', 'Top / Right / Bottom / Left'], @@ -81,7 +86,9 @@ module.exports = { Borders: ['Border Radius', 'Border Width', 'Border Color'], Tables: ['Border Spacing'], Transforms: ['Scale'], + Typography: ['Text Overflow', 'Whitespace'] }; + // We don't want to affect other rules by object reference const cloned = JSON.parse(JSON.stringify(defaultGroups)); const targetGroups = cloned.filter((g) => Object.keys(targetProperties).includes(g.type)); @@ -210,9 +217,49 @@ module.exports = { }); const validated = []; + + // Handle sets of classnames with different parent types + let remaining = parsed + for (const [inputSet, outputClassname] of complexEquivalences) { + if (remaining.length < inputSet.length) { + continue + } + + const parsedElementsInInputSet = remaining.filter(remainingClass => inputSet.some(inputClass => remainingClass.name.includes(inputClass))) + + // Make sure all required classes for the shorthand are present + if (parsedElementsInInputSet.length !== inputSet.length) { + continue + } + + // Make sure the classes share all the same variants + if (new Set(parsedElementsInInputSet.map(p => p.variants)).size !== 1) { + continue + } + + // Make sure the classes share all the same importance + if (new Set(parsedElementsInInputSet.map(p => p.important)).size !== 1) { + continue + } + + const index = parsedElementsInInputSet[0].index + const variants = parsedElementsInInputSet[0].variants + const important = parsedElementsInInputSet[0].important ? "!" : "" + + const patchedClassname = `${variants}${important}${mergedConfig.prefix}${outputClassname}` + troubles.push([parsedElementsInInputSet.map((c) => `${c.name}`), patchedClassname]); + + const validatedClassname = groupUtil.parseClassname(patchedClassname, targetGroups, mergedConfig, index) + validated.push(validatedClassname); + + remaining = remaining.filter(p => !parsedElementsInInputSet.includes(p)) + } + + // Handle sets of classnames with the same parent type + // Each group parentType const checkedGroups = []; - parsed.forEach((classname) => { + remaining.forEach((classname) => { // Valid candidate if (classname.parentType === '') { validated.push(classname); diff --git a/tests/lib/rules/enforces-shorthand.js b/tests/lib/rules/enforces-shorthand.js index db3d297d..f21411f7 100644 --- a/tests/lib/rules/enforces-shorthand.js +++ b/tests/lib/rules/enforces-shorthand.js @@ -112,6 +112,20 @@ ruleTester.run("shorthands", rule, { `, }, + { + code: ` +
+ Possible shorthand available for truncate, but some of the classes have modifiers +
+ `, + }, + { + code: ` +
+ Possible shorthand available for truncate, but some of the classes have important +
+ `, + }, ], invalid: [ @@ -601,5 +615,75 @@ ruleTester.run("shorthands", rule, { `, errors: [generateError(["group/name:rounded-r-full", "group/name:rounded-l-full"], "group/name:rounded-full")], }, + { + code: ` +
+ Possible shorthand when using truncate +
+ `, + output: ` +
+ Possible shorthand when using truncate +
+ `, + errors: [generateError(["overflow-hidden", "text-ellipsis", "whitespace-nowrap"], "truncate")], + }, + { + code: ` +
+ Possible shorthand when using truncate with breakpoint +
+ `, + output: ` +
+ Possible shorthand when using truncate with breakpoint +
+ `, + errors: [generateError(["md:overflow-hidden", "md:text-ellipsis", "md:whitespace-nowrap"], "md:truncate")], + }, + { + code: ` +
+ Possible shorthand when using truncate with hover +
+ `, + output: ` +
+ Possible shorthand when using truncate with hover +
+ `, + errors: [generateError(["hover:overflow-hidden", "hover:text-ellipsis", "hover:whitespace-nowrap"], "hover:truncate")], + }, + { + code: ` +
+ Possible shorthand when using truncate with hover, breakpoint, important and prefix +
+ `, + output: ` +
+ Possible shorthand when using truncate with hover, breakpoint, important and prefix +
+ `, + errors: [generateError(["hover:sm:!tw-overflow-hidden", "hover:sm:!tw-text-ellipsis", "hover:sm:!tw-whitespace-nowrap"], "hover:sm:!tw-truncate")], + options: [ + { + config: { prefix: "tw-" }, + }, + ], + }, + { + code: ` +
+ Possible shorthand when using truncate, tested with additional classnames +
+ `, + output: ` +
+ Possible shorthand when using truncate, tested with additional classnames +
+ `, + errors: [generateError(["overflow-hidden", "text-ellipsis", "whitespace-nowrap"], "truncate")], + }, ], });