eslint-plugin-tailwindcss
Advanced tools
Comparing version 3.6.0-beta.0 to 3.6.0-beta.1
/** | ||
* @fileoverview Default grouping for Tailwind CSS classnames following the order of the official docs | ||
* @fileoverview Default groups for Tailwind CSS classnames | ||
* @description The hierarchy of `members` can be useful to detect redundant and/or contradicting classnames | ||
* @version v3.0.0 | ||
* @version v3.1.3 | ||
* @see https://tailwindcss.com/docs | ||
@@ -6,0 +6,0 @@ * @author François Massart |
@@ -13,2 +13,3 @@ /** | ||
const getOption = require('../util/settings'); | ||
const groups = require('../config/groups').groups; | ||
const parserUtil = require('../util/parser'); | ||
@@ -53,6 +54,2 @@ const order = require('../util/prettier/order'); | ||
}, | ||
groups: { | ||
type: 'array', | ||
items: { type: 'object' }, | ||
}, | ||
removeDuplicates: { | ||
@@ -76,3 +73,3 @@ default: true, | ||
const twConfig = getOption(context, 'config'); | ||
const groupsConfig = getOption(context, 'groups'); | ||
const groupsConfig = groups; | ||
const removeDuplicates = getOption(context, 'removeDuplicates'); | ||
@@ -92,126 +89,2 @@ | ||
/** | ||
* Get the index of a variant within a className | ||
* @param {String} str The input string (haystack) | ||
* @param {Array} arr The list of possible variants (needle) | ||
* @param {Boolean} beginning Optional, starts from the beginning | ||
* @returns {Number} | ||
*/ | ||
const getPrefixIndex = (str, arr, beginning = false) => { | ||
const start = beginning ? '^' : ''; | ||
let idx = arr.findIndex((el) => { | ||
const separator = '\\' + mergedConfig.separator; | ||
const pattern = `${start}${el}${separator}.*`; | ||
const re = new RegExp(pattern); | ||
return re.test(str); | ||
}, str); | ||
return idx; | ||
}; | ||
/** | ||
* Add left '0' padding to given number | ||
* @param {Number} num The input number | ||
* @param {Number} size Optional, the desired length | ||
* @returns {String} | ||
*/ | ||
const pad = (num, size = 2) => { | ||
let str = '' + num; | ||
while (str.length < size) { | ||
str = '0' + str; | ||
} | ||
return str; | ||
}; | ||
/** | ||
* Generate an Array of Array, grouping class by responsive variants | ||
* @param {Array} classNames | ||
* @returns {Array} an Array (one entry per responsive variant), each entry is an array of classnames | ||
*/ | ||
const getResponsiveGroups = (classNames) => { | ||
const responsiveVariants = Object.keys(mergedConfig.theme.screens); | ||
const classnamesByResponsive = [[]]; | ||
responsiveVariants.forEach((prefix) => { | ||
classnamesByResponsive.push([]); | ||
}); | ||
classNames.forEach((cls) => { | ||
const idx = parseInt(getSpecificity(cls, responsiveVariants, true), 10); | ||
classnamesByResponsive[idx].push(cls); | ||
}); | ||
return classnamesByResponsive; | ||
}; | ||
/** | ||
* Parse each classname and populate the `sorted` and `extra` arrays | ||
* @param {Array} classNames | ||
* @returns {Object} An object with `sorted` and `extra` arrays | ||
*/ | ||
const getSortedGroups = (classNames) => { | ||
// Init assets before sorting | ||
const groups = groupUtil.getGroups(groupsConfig, mergedConfig); | ||
const sorted = groupUtil.initGroupSlots(groups); | ||
const extras = []; | ||
// Move each classname inside its dedicated group | ||
classNames.forEach((className) => { | ||
const trimmed = className.replace(/\s{1,}/g, ''); | ||
const idx = groupUtil.getGroupIndex(trimmed, groups, mergedConfig.separator); | ||
if (idx > -1) { | ||
sorted[idx].push(className); | ||
} else { | ||
extras.push(className); | ||
} | ||
}); | ||
// Sorts each groups' classnames | ||
sorted.forEach((slot) => { | ||
slot.sort(sortTailwindClasses); | ||
}); | ||
return { | ||
sorted, | ||
extras, | ||
}; | ||
}; | ||
/** | ||
* Get a padded string version of the sorting key | ||
* @param {String} classname | ||
* @param {Array} variants | ||
* @param {Boolean} beginning | ||
* @returns {String} | ||
*/ | ||
const getSpecificity = (classname, variants, beginning = false) => { | ||
// Index can be -1, 0... Adding 1 for better readability... -1 becomes 0 | ||
return pad(getPrefixIndex(classname, variants, beginning) + 1, 2); | ||
}; | ||
/** | ||
* Sort classnames by using a sorting key | ||
* @param {String} a A classname | ||
* @param {String} b Another classname | ||
* @returns {Number} -1, 0 or +1 | ||
*/ | ||
const sortTailwindClasses = (a, b) => { | ||
const responsiveVariants = Object.keys(mergedConfig.theme.screens); | ||
const themeVariants = ['dark']; | ||
// motion-safe/reduce are not present... | ||
// TODO Check if already present due to custom config overwiting the default `variantOrder` | ||
const stateVariants = [...mergedConfig.variantOrder, 'motion-safe', 'motion-reduce']; | ||
const aIdxStr = `${getSpecificity(a, responsiveVariants, true)}${getSpecificity( | ||
a, | ||
themeVariants | ||
)}${getSpecificity(a, stateVariants)}`; | ||
const bIdxStr = `${getSpecificity(b, responsiveVariants, true)}${getSpecificity( | ||
b, | ||
themeVariants | ||
)}${getSpecificity(b, stateVariants)}`; | ||
const aIdx = parseInt(aIdxStr, 10); | ||
const bIdx = parseInt(bIdxStr, 10); | ||
if (aIdx < bIdx) { | ||
return -1; | ||
} | ||
if (aIdx > bIdx) { | ||
return 1; | ||
} | ||
return 0; | ||
}; | ||
/** | ||
* Recursive function crawling into child nodes | ||
@@ -333,6 +206,10 @@ * @param {ASTNode} node The root node of the current parsing | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
sortNodeArgumentValue(node); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
sortNodeArgumentValue(node); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
sortNodeArgumentValue(node, node.value.expression); | ||
} | ||
}; | ||
@@ -339,0 +216,0 @@ |
@@ -145,6 +145,10 @@ /** | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
parseForNegativeArbitraryClassNames(node); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
parseForNegativeArbitraryClassNames(node); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
parseForNegativeArbitraryClassNames(node, node.value.expression); | ||
} | ||
}; | ||
@@ -151,0 +155,0 @@ |
@@ -373,6 +373,10 @@ /** | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
parseForShorthandCandidates(node); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
parseForShorthandCandidates(node); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
parseForShorthandCandidates(node, node.value.expression); | ||
} | ||
}; | ||
@@ -379,0 +383,0 @@ |
@@ -258,6 +258,10 @@ /** | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
parseForObsoleteClassNames(node); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
parseForObsoleteClassNames(node); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
parseForObsoleteClassNames(node, node.value.expression); | ||
} | ||
}; | ||
@@ -264,0 +268,0 @@ |
@@ -144,6 +144,10 @@ /** | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
parseForArbitraryValues(node); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
parseForArbitraryValues(node); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
parseForArbitraryValues(node, node.value.expression); | ||
} | ||
}; | ||
@@ -150,0 +154,0 @@ |
@@ -131,6 +131,10 @@ /** | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
astUtil.parseNodeRecursive(node, null, parseForContradictingClassNames, true); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
astUtil.parseNodeRecursive(node, null, parseForContradictingClassNames, true); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
astUtil.parseNodeRecursive(node, node.value.expression, parseForContradictingClassNames, true); | ||
} | ||
}; | ||
@@ -137,0 +141,0 @@ |
@@ -140,6 +140,10 @@ /** | ||
const attributeVisitor = function (node) { | ||
if (!astUtil.isValidJSXAttribute(node)) { | ||
if (!astUtil.isClassAttribute(node)) { | ||
return; | ||
} | ||
astUtil.parseNodeRecursive(node, null, parseForCustomClassNames); | ||
if (astUtil.isLiteralAttributeValue(node)) { | ||
astUtil.parseNodeRecursive(node, null, parseForCustomClassNames); | ||
} else if (node.value.type === 'JSXExpressionContainer') { | ||
astUtil.parseNodeRecursive(node, node.value.expression, parseForCustomClassNames); | ||
} | ||
}; | ||
@@ -146,0 +150,0 @@ |
@@ -262,2 +262,4 @@ /** | ||
extractClassnamesFromValue, | ||
isClassAttribute, | ||
isLiteralAttributeValue, | ||
isValidJSXAttribute, | ||
@@ -264,0 +266,0 @@ isValidVueAttribute, |
'use strict'; | ||
const defaultGroups = require('../config/groups').groups; | ||
@@ -22,4 +21,2 @@ function getOption(context, name) { | ||
return ['**/*.css', '!**/node_modules', '!**/.*', '!**/dist', '!**/build']; | ||
case 'groups': | ||
return defaultGroups; | ||
case 'removeDuplicates': | ||
@@ -26,0 +23,0 @@ return true; |
{ | ||
"name": "eslint-plugin-tailwindcss", | ||
"version": "3.6.0-beta.0", | ||
"version": "3.6.0-beta.1", | ||
"description": "Rules enforcing best practices while using Tailwind CSS", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -42,3 +42,3 @@ # eslint-plugin-tailwindcss | ||
> Custom `dark` class, `.grid-flow-dense`, `.text-start`, `.text-end`, `.mix-blend-plus-lighter`, `.border-spacing...` | ||
- BREAKING CHANGE: `groupByResponsive`, `officialSorting` and `prependCustom` are deprecated β οΈ. The official sorting is always used for `classnames-order`. | ||
- BREAKING CHANGE: `groups`, `groupByResponsive`, `officialSorting` and `prependCustom` are deprecated β οΈ. The official sorting from `prettier-plugin-tailwindcss` is always used by `classnames-order`. | ||
> This was required in order to support classnames generated by plugins. | ||
@@ -161,14 +161,19 @@ - FIX: [Many fixes](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/132) including support for classnames generated by plugins. | ||
{ | ||
"settings": { | ||
"tailwindcss": { | ||
settings: { | ||
tailwindcss: { | ||
// These are the default values but feel free to customize | ||
"callees": ["classnames", "clsx", "ctl"], | ||
"config": "tailwind.config.js", | ||
"cssFiles": ["**/*.css", "!**/node_modules", "!**/.*", "!**/dist", "!**/build"], | ||
"cssFilesRefreshRate": 5_000, | ||
"groups": defaultGroups, // imported from groups.js | ||
"removeDuplicates": true, | ||
"whitelist": [] | ||
} | ||
} | ||
callees: ["classnames", "clsx", "ctl"], | ||
config: "tailwind.config.js", | ||
cssFiles: [ | ||
"**/*.css", | ||
"!**/node_modules", | ||
"!**/.*", | ||
"!**/dist", | ||
"!**/build", | ||
], | ||
cssFilesRefreshRate: 5_000, | ||
removeDuplicates: true, | ||
whitelist: [], | ||
}, | ||
}, | ||
} | ||
@@ -175,0 +180,0 @@ ``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
209
152220
4251