tailwind-class-cleaner 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Tailwind Class Cleaner
2
+
3
+ Automatically detect and fix conflicting Tailwind CSS classes.
4
+
5
+ Works in two ways:
6
+
7
+ * 🔍 ESLint → detect + auto-fix while coding
8
+ * ⚡ Babel → optimize during build
9
+
10
+ ---
11
+
12
+ ## 🚀 Installation
13
+
14
+ ```bash
15
+ npm install tailwind-class-cleaner
16
+ ```
17
+
18
+ ---
19
+
20
+ ## 🧠 What It Does
21
+
22
+ Fixes class conflicts like:
23
+
24
+ ```jsx
25
+ <div className="p-2 p-4 text-sm text-lg bg-red-500 bg-blue-500" />
26
+ ```
27
+
28
+ ➡️ Becomes:
29
+
30
+ ```jsx
31
+ <div className="p-4 text-lg bg-blue-500" />
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 🔍 ESLint Usage (Recommended)
37
+
38
+ ### 1. Add plugin
39
+
40
+ In your `.eslintrc.json`:
41
+
42
+ ```json
43
+ {
44
+ "plugins": ["tailwind-cleaner"],
45
+ "rules": {
46
+ "tailwind-cleaner/no-conflicts": "warn"
47
+ }
48
+ }
49
+ ```
50
+
51
+ ---
52
+
53
+ ### 2. Run ESLint
54
+
55
+ ```bash
56
+ npx eslint . --fix
57
+ ```
58
+
59
+ ---
60
+
61
+ ### 💡 Result
62
+
63
+ * Shows warnings in editor
64
+ * Auto-fixes on save / fix
65
+
66
+ ---
67
+
68
+ ## ⚡ Babel Usage (Build-time Optimization)
69
+
70
+ Add to your Babel config:
71
+
72
+ ```json
73
+ {
74
+ "plugins": ["tailwind-class-cleaner/babel"]
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ⚠️ Limitations (v1)
81
+
82
+ * Only supports static strings:
83
+
84
+ ```jsx
85
+ className="p-2 p-4"
86
+ ```
87
+
88
+ * Does NOT yet support:
89
+
90
+ * template literals
91
+ * classnames()
92
+ * conditional classes
93
+
94
+ ---
95
+
96
+ ## 🔭 Roadmap
97
+
98
+ * Support for template literals
99
+ * px vs p conflict handling
100
+ * Tailwind config awareness
101
+ * Vite plugin
102
+
103
+ ---
104
+
105
+ ## 📄 License
106
+
107
+ MIT
108
+ "# Tailwind-class-conflict-cleaner"
package/babel/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import parse from "../core/parser.js";
2
+ import resolve from "../core/resolver.js";
3
+
4
+ export default function () {
5
+ return {
6
+ visitor: {
7
+ JSXAttribute(path) {
8
+ if (path.node.name.name !== "className") return;
9
+
10
+ const valueNode = path.node.value;
11
+
12
+ if (!valueNode || valueNode.type !== "StringLiteral") return;
13
+
14
+ const original = valueNode.value;
15
+
16
+ const newClass = resolve(parse(original)).join(" ");
17
+
18
+ if (newClass !== original) {
19
+ path.node.value.value = newClass;
20
+ }
21
+ }
22
+ }
23
+ };
24
+ }
package/core/groups.js ADDED
@@ -0,0 +1,8 @@
1
+ export const groups = {
2
+
3
+ padding: ["p", "px", "py", "pt", "pb", "pl", "pr"],
4
+
5
+ margin: ["m", "mx", "my", "mt", "mb", "ml", "mr"],
6
+ text: ["text"],
7
+ bg: ["bg"],
8
+ };
package/core/parser.js ADDED
@@ -0,0 +1,5 @@
1
+ export default function parse(classString) {
2
+ if (!classString || typeof classString !== "string") return [];
3
+
4
+ return classString.trim().split(/\s+/).filter(Boolean);
5
+ }
@@ -0,0 +1,48 @@
1
+ import { groups } from "./groups.js";
2
+
3
+ export default function resolve(classList) {
4
+ const finalClasses = [];
5
+
6
+ for (const cls of classList) {
7
+ const prefix = getPrefix(cls);
8
+
9
+ const groupKey = findGroup(prefix);
10
+
11
+ if (!groupKey) {
12
+ finalClasses.push(cls);
13
+ continue;
14
+ }
15
+ removeSamePrefix(finalClasses, prefix);
16
+
17
+ finalClasses.push(cls);
18
+ }
19
+
20
+ return finalClasses;
21
+ }
22
+
23
+
24
+ //get prefix: p-4 → p, px-2 → px
25
+ function getPrefix(cls) {
26
+ return cls.split("-")[0];
27
+ }
28
+
29
+
30
+ //find group (padding, margin, etc.)
31
+ function findGroup(prefix) {
32
+ for (const key in groups) {
33
+ if (groups[key].includes(prefix)) {
34
+ return key;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+
41
+ //remove only exact prefix conflicts
42
+ function removeSamePrefix(arr, prefix) {
43
+ for (let i = arr.length - 1; i >= 0; i--) {
44
+ if (getPrefix(arr[i]) === prefix) {
45
+ arr.splice(i, 1);
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,7 @@
1
+ import noConflicts from "./rules/no-conflict.js";
2
+
3
+ export default {
4
+ rules: {
5
+ "no-conflicts": noConflicts,
6
+ },
7
+ };
@@ -0,0 +1,84 @@
1
+ import parse from "../../core/parser.js";
2
+ import resolve from "../../core/resolver.js";
3
+
4
+ export default {
5
+ meta: {
6
+ type: "problem",
7
+ fixable: "code",
8
+ },
9
+
10
+ create(context) {
11
+ return {
12
+ JSXAttribute(node) {
13
+ if (node.name.name !== "className") return;
14
+
15
+ const value = node.value;
16
+
17
+ if (value?.type === "Literal" && typeof value.value === "string") {
18
+ handleString(context, node, value.value);
19
+ }
20
+
21
+ if (value?.type === "JSXExpressionContainer" &&
22
+ value.expression.type === "TemplateLiteral") {
23
+ handleTemplate(context, node, value.expression);
24
+ }
25
+ },
26
+ };
27
+ },
28
+ };
29
+
30
+
31
+ function handleString(context, node, original) {
32
+ const newClass = resolve(parse(original)).join(" ");
33
+
34
+ if (newClass === original) return;
35
+
36
+ context.report({
37
+ node,
38
+ message: `Tailwind conflict: "${original}" → "${newClass}"`,
39
+ fix(fixer) {
40
+ return fixer.replaceText(node.value, `"${newClass}"`);
41
+ },
42
+ });
43
+ }
44
+
45
+
46
+ function handleTemplate(context, node, template) {
47
+ const quasis = template.quasis;
48
+
49
+ let changed = false;
50
+
51
+ const newQuasis = quasis.map((q) => {
52
+ const original = q.value.raw;
53
+
54
+ const resolved = resolve(parse(original)).join(" ");
55
+
56
+ if (resolved !== original) {
57
+ changed = true;
58
+ }
59
+
60
+ return resolved;
61
+ });
62
+
63
+ if (!changed) return;
64
+
65
+ context.report({
66
+ node,
67
+ message: "Tailwind conflicts in template literal",
68
+ fix(fixer) {
69
+ let result = "`";
70
+
71
+ for (let i = 0; i < newQuasis.length; i++) {
72
+ result += newQuasis[i];
73
+
74
+ if (template.expressions[i]) {
75
+ result += "${" + context.sourceCode.getText(template.expressions[i]) + "}";
76
+ }
77
+ }
78
+
79
+ result += "`";
80
+
81
+ return fixer.replaceText(node.value, `{${result}}`);
82
+ },
83
+ });
84
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "tailwind-class-cleaner",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+
6
+ "main": "eslint/index.js",
7
+
8
+ "exports": {
9
+ ".": "./eslint/index.js",
10
+ "./babel": "./babel/index.js"
11
+ },
12
+
13
+ "files": [
14
+ "eslint",
15
+ "babel",
16
+ "core"
17
+ ],
18
+
19
+ "peerDependencies": {
20
+ "eslint": ">=8.0.0",
21
+ "@babel/core": "^7.0.0"
22
+ }
23
+ }