Skip to content

Commit ab1e22e

Browse files
Merge branch 'master' of github.com:francoismassart/eslint-plugin-tailwindcss into fix/issue-40-jit-arbitrary-values
# Conflicts: # tests/lib/rules/no-contradicting-classname.js
2 parents eee2e29 + 6591ae8 commit ab1e22e

14 files changed

+426
-11
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ Rules enforcing best practices and consistency using [Tailwind CSS](https://tail
1212
1313
## Latest changelog
1414

15-
- Add [`groupByResponsive`](https://github.com/francoismassart/eslint-plugin-tailwindcss/blob/master/docs/rules/classnames-order.md#groupbyresponsive-default-false) option (default: `false`) in `v1.13.0`
15+
- [Performance gains](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/42) on `no-custom-classname` and `no-contradicting-classname` (by [larrifax](https://github.com/larrifax) 🙏)
16+
17+
- Add support for [tagged templates](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/41) (by [larrifax](https://github.com/larrifax) 🙏)
18+
19+
- [Include "plugins": ["tailwindcss"]](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/44) in the `recommended` preset(by [kripod](https://github.com/kripod) 🙏)
20+
21+
- [Support dark class](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/43) when `darkMode` is set to `class`
1622

1723
[View all releases on github](https://github.com/francoismassart/eslint-plugin-tailwindcss/releases)
1824

docs/rules/no-custom-classname.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ Examples of **correct** code for this rule:
2222

2323
```js
2424
...
25-
"tailwindcss/classnames-order": [<enabled>, {
25+
"tailwindcss/no-custom-classname": [<enabled>, {
2626
"callees": Array<string>,
2727
"config": <string>|<object>,
28+
"cssFiles": Array<string>,
2829
"whitelist": Array<string>
2930
}]
3031
...

lib/config/groups.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ module.exports.groups = [
1313
type: 'Hover, Focus, & Other States',
1414
members: 'group',
1515
},
16+
{
17+
type: 'Dark Mode',
18+
members: '${dark}',
19+
},
1620
],
1721
},
1822
{
@@ -132,7 +136,7 @@ module.exports.groups = [
132136
},
133137
{
134138
type: 'Z-Index',
135-
members: 'z\\-${zIndex}',
139+
members: '(z\\-${zIndex}|\\-z\\-${-zIndex})',
136140
},
137141
],
138142
},
@@ -362,6 +366,14 @@ module.exports.groups = [
362366
type: 'space-x',
363367
members: '(space\\-x\\-${space}|\\-space\\-x\\-${-space})',
364368
},
369+
{
370+
type: 'space-y-reverse',
371+
members: 'space\\-y\\-reverse',
372+
},
373+
{
374+
type: 'space-x-reverse',
375+
members: 'space\\-x\\-reverse',
376+
},
365377
],
366378
},
367379
],

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
},
1919
configs: {
2020
recommended: {
21+
plugins: ['tailwindcss'],
2122
parserOptions: {
2223
ecmaFeatures: {
2324
jsx: true,

lib/rules/classnames-order.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,19 @@ module.exports = {
5858
default: true,
5959
type: 'boolean',
6060
},
61+
tags: {
62+
type: 'array',
63+
items: { type: 'string', minLength: 0 },
64+
uniqueItems: true,
65+
},
6166
},
6267
},
6368
],
6469
},
6570

6671
create: function (context) {
6772
const callees = getOption(context, 'callees');
73+
const tags = getOption(context, 'tags');
6874
const twConfig = getOption(context, 'config');
6975
const groupsConfig = getOption(context, 'groups');
7076
const groupByResponsive = getOption(context, 'groupByResponsive');
@@ -333,6 +339,13 @@ module.exports = {
333339
sortNodeArgumentValue(node, arg);
334340
});
335341
},
342+
TaggedTemplateExpression: function (node) {
343+
if (!tags.includes(node.tag.name)) {
344+
return;
345+
}
346+
347+
sortNodeArgumentValue(node, node.quasi);
348+
},
336349
};
337350
const templateVisitor = {
338351
VAttribute: function (node) {

lib/rules/no-contradicting-classname.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,19 @@ module.exports = {
4646
default: 'tailwind.config.js',
4747
type: ['string', 'object'],
4848
},
49+
tags: {
50+
type: 'array',
51+
items: { type: 'string', minLength: 0 },
52+
uniqueItems: true,
53+
},
4954
},
5055
},
5156
],
5257
},
5358

5459
create: function (context) {
5560
const callees = getOption(context, 'callees');
61+
const tags = getOption(context, 'tags');
5662
const twConfig = getOption(context, 'config');
5763

5864
const mergedConfig = customConfig.resolve(twConfig);
@@ -61,6 +67,9 @@ module.exports = {
6167
// Helpers
6268
//----------------------------------------------------------------------
6369

70+
// Init assets before sorting
71+
const groups = groupUtil.getGroups(defaultGroups, mergedConfig);
72+
6473
/**
6574
* Parse the classnames and report found conflicts
6675
* @param {Array} classNames
@@ -69,7 +78,6 @@ module.exports = {
6978
const parseForContradictingClassNames = (classNames, node) => {
7079
classNames = attrUtil.sanitizeClassnames(classNames);
7180
// Init assets before sorting
72-
const groups = groupUtil.getGroups(defaultGroups, mergedConfig);
7381
const sorted = groupUtil.initGroupSlots(groups);
7482

7583
// Move each classname inside its dedicated group
@@ -147,6 +155,24 @@ module.exports = {
147155
});
148156
parseForContradictingClassNames(allClassnamesForNode, node);
149157
},
158+
TaggedTemplateExpression: function (node) {
159+
if (!tags.includes(node.tag.name)) {
160+
return;
161+
}
162+
163+
const allClassnamesForNode = [];
164+
const pushClasses = (classNames, targetNode) => {
165+
if (targetNode === null) {
166+
// Classnames should be parsed in isolation (e.g. conditional expressions)
167+
parseForContradictingClassNames(classNames, node);
168+
} else {
169+
// Gather the classes prior to validation
170+
allClassnamesForNode.push(...classNames);
171+
}
172+
};
173+
astUtil.parseNodeRecursive(node, node.quasi, pushClasses, true);
174+
parseForContradictingClassNames(allClassnamesForNode, node);
175+
},
150176
};
151177

152178
const templateVisitor = {

lib/rules/no-custom-classname.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ module.exports = {
5252
items: { type: 'string', minLength: 0 },
5353
uniqueItems: true,
5454
},
55+
tags: {
56+
type: 'array',
57+
items: { type: 'string', minLength: 0 },
58+
uniqueItems: true,
59+
},
5560
whitelist: {
5661
type: 'array',
5762
items: { type: 'string', minLength: 0 },
@@ -64,6 +69,7 @@ module.exports = {
6469

6570
create: function (context) {
6671
const callees = getOption(context, 'callees');
72+
const tags = getOption(context, 'tags');
6773
const twConfig = getOption(context, 'config');
6874
const cssFiles = getOption(context, 'cssFiles');
6975
const whitelist = getOption(context, 'whitelist');
@@ -74,16 +80,17 @@ module.exports = {
7480
// Helpers
7581
//----------------------------------------------------------------------
7682

83+
// Init assets before sorting
84+
const groups = groupUtil.getGroups(defaultGroups, mergedConfig);
85+
const classnamesFromFiles = getClassnamesFromCSS(cssFiles);
86+
7787
/**
7888
* Parse the classnames and report found conflicts
7989
* @param {Array} classNames
8090
* @param {ASTNode} node
8191
*/
8292
const parseForCustomClassNames = (classNames, node) => {
8393
classNames = attrUtil.sanitizeClassnames(classNames);
84-
// Init assets before sorting
85-
const groups = groupUtil.getGroups(defaultGroups, mergedConfig);
86-
const classnamesFromFiles = getClassnamesFromCSS(cssFiles);
8794

8895
classNames.forEach((className) => {
8996
const idx = groupUtil.getGroupIndex(className, groups, mergedConfig.separator);
@@ -128,6 +135,12 @@ module.exports = {
128135
astUtil.parseNodeRecursive(node, arg, parseForCustomClassNames);
129136
});
130137
},
138+
TaggedTemplateExpression: function (node) {
139+
if (!tags.includes(node.tag.name)) {
140+
return;
141+
}
142+
astUtil.parseNodeRecursive(node, node.quasi, parseForCustomClassNames);
143+
},
131144
};
132145

133146
const templateVisitor = {

lib/util/groupMethods.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ function generateOptions(propName, keys, config, isNegative = false) {
3131
keys.splice(defaultKeyIndex, 1);
3232
}
3333
switch (propName) {
34+
case 'dark':
35+
return config.darkMode === 'class' ? 'dark' : '';
3436
case 'placeholderColor':
3537
case 'textColor':
3638
case 'backgroundColor':
@@ -91,9 +93,14 @@ function patchRegex(re, config) {
9193
}
9294
// e.g. backgroundColor, letterSpacing...
9395
props.forEach((prop) => {
96+
const token = new RegExp('\\$\\{' + prop + '\\}');
9497
const isNegative = prop.substr(0, 1) === '-';
9598
const patchedProp = isNegative ? prop.substr(1) : prop;
96-
if (!config.theme || !config.theme[patchedProp]) {
99+
if (prop === 'dark') {
100+
// Special case, not a default property from the theme
101+
replaced = replaced.replace(token, generateOptions(patchedProp, [], config, isNegative));
102+
return patched + replaced;
103+
} else if (!config.theme || !config.theme[patchedProp]) {
97104
// prop not found in config
98105
return;
99106
}
@@ -104,7 +111,6 @@ function patchRegex(re, config) {
104111
const absoluteKeys = keys.map((k) => {
105112
return isNegative ? k.substr(1) : k;
106113
});
107-
const token = new RegExp('\\$\\{' + prop + '\\}');
108114
if (keys.length === 0 || replaced.match(token) === null) {
109115
// empty array
110116
return;

lib/util/settings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ function getOption(context, name) {
2727
return false;
2828
case 'removeDuplicates':
2929
return true;
30+
case 'tags':
31+
return [];
3032
case 'whitelist':
3133
return [];
3234
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)