cleanwind 0.1.3 → 0.3.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 +31 -1
- package/dist/index.js +534 -92
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ Clean imports and Tailwind CSS class names from the command line.
|
|
|
4
4
|
|
|
5
5
|
`cleanwind` removes duplicate import specifiers, merges imports from the same
|
|
6
6
|
module, sorts import groups, deduplicates Tailwind CSS classes, sorts utilities,
|
|
7
|
-
|
|
7
|
+
reports conflicting classes, cleans static class strings in common helper calls,
|
|
8
|
+
and can format changed files with Prettier.
|
|
8
9
|
|
|
9
10
|
## Installation
|
|
10
11
|
|
|
@@ -20,6 +21,7 @@ For a local project install, run cleanwind through your package manager:
|
|
|
20
21
|
npx cleanwind check
|
|
21
22
|
npx cleanwind fix
|
|
22
23
|
npx cleanwind fix --staged
|
|
24
|
+
npx cleanwind fix --format
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
Or with npm/pnpm/yarn/bun:
|
|
@@ -62,10 +64,12 @@ npx cleanwind check --cwd ./apps/web
|
|
|
62
64
|
npx cleanwind check --config ./cleanwind.config.ts
|
|
63
65
|
npx cleanwind check --write
|
|
64
66
|
npx cleanwind check --verbose
|
|
67
|
+
npx cleanwind check --format
|
|
65
68
|
|
|
66
69
|
npx cleanwind fix
|
|
67
70
|
npx cleanwind fix --staged
|
|
68
71
|
npx cleanwind fix --check
|
|
72
|
+
npx cleanwind fix --format
|
|
69
73
|
npx cleanwind fix --cwd ./apps/web --verbose
|
|
70
74
|
```
|
|
71
75
|
|
|
@@ -73,3 +77,29 @@ npx cleanwind fix --cwd ./apps/web --verbose
|
|
|
73
77
|
|
|
74
78
|
The published CLI bundles the cleanwind core engine, so installing `cleanwind`
|
|
75
79
|
does not require a separate core package.
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
export default {
|
|
85
|
+
imports: true,
|
|
86
|
+
tailwind: true,
|
|
87
|
+
removeDuplicateImports: true,
|
|
88
|
+
removeUnusedImports: false,
|
|
89
|
+
removeDuplicateClasses: true,
|
|
90
|
+
sortImports: true,
|
|
91
|
+
importAliases: ["@/"],
|
|
92
|
+
sortTailwindClasses: true,
|
|
93
|
+
tailwindFunctions: ["clsx", "classnames", "cva", "cn", "twMerge"],
|
|
94
|
+
detectConflicts: true,
|
|
95
|
+
format: false
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `removeUnusedImports: true` only when you want cleanwind to remove import
|
|
100
|
+
specifiers that appear unused. Use `format: "prettier"` or `--format` to format
|
|
101
|
+
output with Prettier after cleanup.
|
|
102
|
+
|
|
103
|
+
Use `importAliases` for project aliases such as `@/`, `~/`, or `~app/`. Use
|
|
104
|
+
`tailwindFunctions` to control which helper calls cleanwind scans for static
|
|
105
|
+
class strings.
|
package/dist/index.js
CHANGED
|
@@ -7,14 +7,15 @@ import { Command } from "commander";
|
|
|
7
7
|
import { existsSync } from "fs";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { createJiti } from "jiti";
|
|
10
|
-
import * as
|
|
10
|
+
import * as t from "@babel/types";
|
|
11
11
|
import MagicString from "magic-string";
|
|
12
12
|
import { parse } from "@babel/parser";
|
|
13
|
-
import * as t from "@babel/types";
|
|
14
13
|
import * as t3 from "@babel/types";
|
|
15
14
|
import MagicString2 from "magic-string";
|
|
15
|
+
import * as t2 from "@babel/types";
|
|
16
16
|
import { promises as fs } from "fs";
|
|
17
17
|
import path3 from "path";
|
|
18
|
+
import * as prettier from "prettier";
|
|
18
19
|
import { execFileSync } from "child_process";
|
|
19
20
|
import path2 from "path";
|
|
20
21
|
import fg from "fast-glob";
|
|
@@ -22,10 +23,14 @@ var defaultConfig = {
|
|
|
22
23
|
imports: true,
|
|
23
24
|
tailwind: true,
|
|
24
25
|
removeDuplicateImports: true,
|
|
26
|
+
removeUnusedImports: false,
|
|
25
27
|
removeDuplicateClasses: true,
|
|
26
28
|
sortImports: true,
|
|
29
|
+
importAliases: ["@/"],
|
|
27
30
|
sortTailwindClasses: true,
|
|
31
|
+
tailwindFunctions: ["clsx", "classnames", "cva", "cn", "twMerge"],
|
|
28
32
|
detectConflicts: true,
|
|
33
|
+
format: false,
|
|
29
34
|
include: ["src/**/*.{js,jsx,ts,tsx}"],
|
|
30
35
|
exclude: ["node_modules", ".next", "dist"]
|
|
31
36
|
};
|
|
@@ -77,26 +82,6 @@ function parseSource(source) {
|
|
|
77
82
|
});
|
|
78
83
|
}
|
|
79
84
|
var visitorKeys = t.VISITOR_KEYS;
|
|
80
|
-
function walkNode(node, enter, parent) {
|
|
81
|
-
enter(node, parent);
|
|
82
|
-
const keys = visitorKeys[node.type] ?? [];
|
|
83
|
-
const record = node;
|
|
84
|
-
for (const key of keys) {
|
|
85
|
-
const value = record[key];
|
|
86
|
-
if (Array.isArray(value)) {
|
|
87
|
-
for (const child of value) {
|
|
88
|
-
if (isNode(child)) {
|
|
89
|
-
walkNode(child, enter, node);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} else if (isNode(value)) {
|
|
93
|
-
walkNode(value, enter, node);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function isNode(value) {
|
|
98
|
-
return typeof value === "object" && value !== null && "type" in value;
|
|
99
|
-
}
|
|
100
85
|
var ImportCleaner = class {
|
|
101
86
|
/** Checks whether imports would change after cleanwind normalization. */
|
|
102
87
|
static check(context) {
|
|
@@ -115,105 +100,277 @@ var ImportCleaner = class {
|
|
|
115
100
|
/** Returns source text with unused, duplicated, and unordered imports normalized. */
|
|
116
101
|
static fix(context) {
|
|
117
102
|
const ast = parseSource(context.source);
|
|
118
|
-
const imports = ast.program.body.filter(
|
|
103
|
+
const imports = ast.program.body.filter(t.isImportDeclaration);
|
|
119
104
|
if (imports.length === 0) {
|
|
120
105
|
return { output: context.source, issues: [] };
|
|
121
106
|
}
|
|
122
|
-
const usedIdentifiers =
|
|
123
|
-
const records = buildImportRecords(imports, usedIdentifiers,
|
|
107
|
+
const usedIdentifiers = context.config.removeUnusedImports ? collectUsedImportIdentifiers(ast) : void 0;
|
|
108
|
+
const records = buildImportRecords(imports, usedIdentifiers, {
|
|
109
|
+
mergeDuplicates: context.config.removeDuplicateImports,
|
|
110
|
+
sortImports: context.config.sortImports,
|
|
111
|
+
importAliases: context.config.importAliases
|
|
112
|
+
});
|
|
124
113
|
const spans = imports.map((declaration) => spanForImport(context.source, declaration));
|
|
125
114
|
const output = replaceImports(context.source, spans, renderImportBlock(records));
|
|
126
115
|
return { output, issues: [] };
|
|
127
116
|
}
|
|
128
117
|
};
|
|
129
|
-
function
|
|
130
|
-
const
|
|
118
|
+
function collectUsedImportIdentifiers(ast) {
|
|
119
|
+
const used = /* @__PURE__ */ new Set();
|
|
120
|
+
const programScope = createScope();
|
|
131
121
|
for (const statement of ast.program.body) {
|
|
132
|
-
if (
|
|
122
|
+
if (!t.isImportDeclaration(statement)) {
|
|
133
123
|
continue;
|
|
134
124
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
125
|
+
for (const specifier of statement.specifiers) {
|
|
126
|
+
programScope.bindings.set(specifier.local.name, "import");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const statement of ast.program.body) {
|
|
130
|
+
if (t.isImportDeclaration(statement)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
collectHoistedBindings(statement, programScope);
|
|
134
|
+
walkUsage(statement, void 0, [programScope], used);
|
|
143
135
|
}
|
|
144
|
-
return
|
|
136
|
+
return used;
|
|
145
137
|
}
|
|
146
138
|
function isIdentifierReference(parent, node) {
|
|
147
139
|
if (!parent) {
|
|
148
140
|
return true;
|
|
149
141
|
}
|
|
150
|
-
if (
|
|
151
|
-
if ((
|
|
142
|
+
if (t.isVariableDeclarator(parent) && parent.id === node) return false;
|
|
143
|
+
if ((t.isFunctionDeclaration(parent) || t.isFunctionExpression(parent)) && parent.id === node)
|
|
152
144
|
return false;
|
|
153
|
-
if ((
|
|
145
|
+
if ((t.isClassDeclaration(parent) || t.isClassExpression(parent)) && parent.id === node)
|
|
154
146
|
return false;
|
|
155
|
-
if (
|
|
156
|
-
if (
|
|
157
|
-
if (
|
|
158
|
-
if (
|
|
159
|
-
if (
|
|
160
|
-
if (
|
|
147
|
+
if (t.isObjectProperty(parent) && parent.key === node && !parent.computed) return false;
|
|
148
|
+
if (t.isObjectMethod(parent) && parent.key === node && !parent.computed) return false;
|
|
149
|
+
if (t.isMemberExpression(parent) && parent.property === node && !parent.computed) return false;
|
|
150
|
+
if (t.isLabeledStatement(parent) && parent.label === node) return false;
|
|
151
|
+
if (t.isTSTypeAliasDeclaration(parent) && parent.id === node) return false;
|
|
152
|
+
if (t.isTSInterfaceDeclaration(parent) && parent.id === node) return false;
|
|
161
153
|
return true;
|
|
162
154
|
}
|
|
163
155
|
function isJSXIdentifierReference(parent, node) {
|
|
164
156
|
if (!parent) {
|
|
165
157
|
return false;
|
|
166
158
|
}
|
|
167
|
-
if (
|
|
159
|
+
if (t.isJSXAttribute(parent)) {
|
|
168
160
|
return false;
|
|
169
161
|
}
|
|
170
|
-
return
|
|
162
|
+
return t.isJSXOpeningElement(parent) && parent.name === node || t.isJSXClosingElement(parent) && parent.name === node || t.isJSXMemberExpression(parent) && parent.object === node;
|
|
163
|
+
}
|
|
164
|
+
function walkUsage(node, parent, scopes, used) {
|
|
165
|
+
if (t.isImportDeclaration(node)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (t.isFunctionDeclaration(node)) {
|
|
169
|
+
declareIdentifier(node.id, currentScope(scopes));
|
|
170
|
+
const functionScope = createChildScope();
|
|
171
|
+
declarePatterns(node.params, functionScope);
|
|
172
|
+
if (node.body) {
|
|
173
|
+
walkUsage(node.body, node, [...scopes, functionScope], used);
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
|
|
178
|
+
const functionScope = createChildScope();
|
|
179
|
+
if (t.isFunctionExpression(node)) {
|
|
180
|
+
declareIdentifier(node.id, functionScope);
|
|
181
|
+
}
|
|
182
|
+
declarePatterns(node.params, functionScope);
|
|
183
|
+
walkChildren(node, parent, [...scopes, functionScope], used, /* @__PURE__ */ new Set(["id", "params"]));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (t.isBlockStatement(node) || t.isProgram(node)) {
|
|
187
|
+
const blockScope = t.isProgram(node) ? currentScope(scopes) : createChildScope();
|
|
188
|
+
collectHoistedBindings(node, blockScope);
|
|
189
|
+
const nextScopes = t.isProgram(node) ? scopes : [...scopes, blockScope];
|
|
190
|
+
walkChildren(node, parent, nextScopes, used);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (t.isVariableDeclarator(node)) {
|
|
194
|
+
declarePattern(node.id, currentScope(scopes));
|
|
195
|
+
if (node.init) {
|
|
196
|
+
walkUsage(node.init, node, scopes, used);
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (t.isClassDeclaration(node)) {
|
|
201
|
+
declareIdentifier(node.id, currentScope(scopes));
|
|
202
|
+
walkChildren(node, parent, scopes, used, /* @__PURE__ */ new Set(["id"]));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (t.isCatchClause(node)) {
|
|
206
|
+
const catchScope = createChildScope();
|
|
207
|
+
if (node.param) {
|
|
208
|
+
declarePattern(node.param, catchScope);
|
|
209
|
+
}
|
|
210
|
+
walkUsage(node.body, node, [...scopes, catchScope], used);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (t.isTSTypeAliasDeclaration(node) || t.isTSInterfaceDeclaration(node)) {
|
|
214
|
+
declareIdentifier(node.id, currentScope(scopes));
|
|
215
|
+
walkChildren(node, parent, scopes, used, /* @__PURE__ */ new Set(["id"]));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (t.isIdentifier(node) && isIdentifierReference(parent, node) && resolvesToImport(node.name, scopes)) {
|
|
219
|
+
used.add(node.name);
|
|
220
|
+
}
|
|
221
|
+
if (t.isJSXIdentifier(node) && isJSXIdentifierReference(parent, node) && resolvesToImport(node.name, scopes)) {
|
|
222
|
+
used.add(node.name);
|
|
223
|
+
}
|
|
224
|
+
walkChildren(node, parent, scopes, used);
|
|
171
225
|
}
|
|
172
|
-
function
|
|
226
|
+
function walkChildren(node, parent, scopes, used, skipKeys = /* @__PURE__ */ new Set()) {
|
|
227
|
+
const keys = visitorKeys[node.type] ?? [];
|
|
228
|
+
const record = node;
|
|
229
|
+
for (const key of keys) {
|
|
230
|
+
if (skipKeys.has(key)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const value = record[key];
|
|
234
|
+
if (Array.isArray(value)) {
|
|
235
|
+
for (const child of value) {
|
|
236
|
+
if (isNode(child)) {
|
|
237
|
+
walkUsage(child, node, scopes, used);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} else if (isNode(value)) {
|
|
241
|
+
walkUsage(value, node, scopes, used);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function collectHoistedBindings(node, scope) {
|
|
246
|
+
const body = t.isProgram(node) || t.isBlockStatement(node) ? node.body : [node];
|
|
247
|
+
for (const statement of body) {
|
|
248
|
+
if (t.isFunctionDeclaration(statement)) {
|
|
249
|
+
declareIdentifier(statement.id, scope);
|
|
250
|
+
} else if (t.isClassDeclaration(statement)) {
|
|
251
|
+
declareIdentifier(statement.id, scope);
|
|
252
|
+
} else if (t.isVariableDeclaration(statement)) {
|
|
253
|
+
for (const declaration of statement.declarations) {
|
|
254
|
+
declarePattern(declaration.id, scope);
|
|
255
|
+
}
|
|
256
|
+
} else if (t.isTSTypeAliasDeclaration(statement) || t.isTSInterfaceDeclaration(statement)) {
|
|
257
|
+
declareIdentifier(statement.id, scope);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function createScope() {
|
|
262
|
+
return { bindings: /* @__PURE__ */ new Map() };
|
|
263
|
+
}
|
|
264
|
+
function createChildScope() {
|
|
265
|
+
return createScope();
|
|
266
|
+
}
|
|
267
|
+
function currentScope(scopes) {
|
|
268
|
+
return scopes[scopes.length - 1] ?? createScope();
|
|
269
|
+
}
|
|
270
|
+
function declareIdentifier(node, scope) {
|
|
271
|
+
if (node) {
|
|
272
|
+
scope.bindings.set(node.name, "local");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function declarePatterns(patterns, scope) {
|
|
276
|
+
for (const pattern of patterns) {
|
|
277
|
+
declarePattern(pattern, scope);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function declarePattern(pattern, scope) {
|
|
281
|
+
if (t.isIdentifier(pattern)) {
|
|
282
|
+
scope.bindings.set(pattern.name, "local");
|
|
283
|
+
} else if (t.isTSParameterProperty(pattern)) {
|
|
284
|
+
declarePattern(pattern.parameter, scope);
|
|
285
|
+
} else if (t.isRestElement(pattern)) {
|
|
286
|
+
declarePattern(pattern.argument, scope);
|
|
287
|
+
} else if (t.isAssignmentPattern(pattern)) {
|
|
288
|
+
declarePattern(pattern.left, scope);
|
|
289
|
+
} else if (t.isObjectPattern(pattern)) {
|
|
290
|
+
for (const property of pattern.properties) {
|
|
291
|
+
if (t.isObjectProperty(property)) {
|
|
292
|
+
declarePattern(property.value, scope);
|
|
293
|
+
} else if (t.isRestElement(property)) {
|
|
294
|
+
declarePattern(property.argument, scope);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} else if (t.isArrayPattern(pattern)) {
|
|
298
|
+
for (const element of pattern.elements) {
|
|
299
|
+
if (element) {
|
|
300
|
+
declarePattern(element, scope);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function resolvesToImport(name, scopes) {
|
|
306
|
+
for (let index = scopes.length - 1; index >= 0; index -= 1) {
|
|
307
|
+
const binding = scopes[index]?.bindings.get(name);
|
|
308
|
+
if (binding) {
|
|
309
|
+
return binding === "import";
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function isNode(value) {
|
|
315
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
316
|
+
}
|
|
317
|
+
function buildImportRecords(declarations, usedIdentifiers, options) {
|
|
173
318
|
const records = /* @__PURE__ */ new Map();
|
|
174
|
-
const
|
|
175
|
-
|
|
319
|
+
const orderedRecords = [];
|
|
320
|
+
let sideEffectChunk = 0;
|
|
321
|
+
declarations.forEach((declaration, order) => {
|
|
176
322
|
const source = declaration.source.value;
|
|
177
323
|
const sideEffect = declaration.specifiers.length === 0;
|
|
178
324
|
if (sideEffect) {
|
|
179
|
-
|
|
325
|
+
orderedRecords.push({
|
|
326
|
+
key: `side-effect:${order}`,
|
|
180
327
|
source,
|
|
181
328
|
named: [],
|
|
182
329
|
sideEffect: true,
|
|
183
|
-
group: classifyImport(source)
|
|
330
|
+
group: classifyImport(source, options.importAliases),
|
|
331
|
+
order
|
|
184
332
|
});
|
|
185
|
-
|
|
333
|
+
sideEffectChunk += 1;
|
|
334
|
+
return;
|
|
186
335
|
}
|
|
187
|
-
const key = source
|
|
336
|
+
const key = options.mergeDuplicates ? `${sideEffectChunk}:${source}` : `${order}:${source}`;
|
|
188
337
|
const record = records.get(key) ?? {
|
|
189
338
|
source,
|
|
339
|
+
key,
|
|
190
340
|
named: [],
|
|
191
341
|
sideEffect: false,
|
|
192
|
-
group: classifyImport(source)
|
|
342
|
+
group: classifyImport(source, options.importAliases),
|
|
343
|
+
order
|
|
193
344
|
};
|
|
194
345
|
for (const specifier of declaration.specifiers) {
|
|
195
346
|
const local = specifier.local.name;
|
|
196
|
-
if (!usedIdentifiers.has(local)) {
|
|
347
|
+
if (usedIdentifiers && !usedIdentifiers.has(local)) {
|
|
197
348
|
continue;
|
|
198
349
|
}
|
|
199
|
-
if (
|
|
350
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
200
351
|
record.defaultName ??= local;
|
|
201
|
-
} else if (
|
|
352
|
+
} else if (t.isImportNamespaceSpecifier(specifier)) {
|
|
202
353
|
record.namespaceName ??= local;
|
|
203
|
-
} else if (
|
|
354
|
+
} else if (t.isImportSpecifier(specifier)) {
|
|
204
355
|
const declarationKind = declaration.importKind === "type" ? "type" : "value";
|
|
205
356
|
record.named.push(importPartFromSpecifier(specifier, declarationKind));
|
|
206
357
|
}
|
|
207
358
|
}
|
|
208
359
|
if (record.defaultName || record.namespaceName || record.named.length > 0) {
|
|
209
|
-
|
|
360
|
+
const nextRecord = options.mergeDuplicates ? dedupeRecord(record) : record;
|
|
361
|
+
records.set(key, nextRecord);
|
|
362
|
+
const existingIndex = orderedRecords.findIndex((item) => item.key === key);
|
|
363
|
+
if (existingIndex >= 0) {
|
|
364
|
+
orderedRecords[existingIndex] = nextRecord;
|
|
365
|
+
} else {
|
|
366
|
+
orderedRecords.push(nextRecord);
|
|
367
|
+
}
|
|
210
368
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return sort ? sortRecords(merged) : merged;
|
|
369
|
+
});
|
|
370
|
+
return options.sortImports ? sortRecords(orderedRecords) : orderedRecords;
|
|
214
371
|
}
|
|
215
372
|
function importPartFromSpecifier(specifier, declarationKind) {
|
|
216
|
-
const imported =
|
|
373
|
+
const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
|
|
217
374
|
return {
|
|
218
375
|
imported,
|
|
219
376
|
local: specifier.local.name,
|
|
@@ -236,20 +393,37 @@ function sortRecords(records) {
|
|
|
236
393
|
alias: 1,
|
|
237
394
|
relative: 2
|
|
238
395
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
396
|
+
const sorted = [];
|
|
397
|
+
let chunk = [];
|
|
398
|
+
const flushChunk = () => {
|
|
399
|
+
sorted.push(
|
|
400
|
+
...chunk.sort((left, right) => {
|
|
401
|
+
const groupDelta = groupRank[left.group] - groupRank[right.group];
|
|
402
|
+
if (groupDelta !== 0) {
|
|
403
|
+
return groupDelta;
|
|
404
|
+
}
|
|
405
|
+
const sourceDelta = left.source.localeCompare(right.source);
|
|
406
|
+
if (sourceDelta !== 0) {
|
|
407
|
+
return sourceDelta;
|
|
408
|
+
}
|
|
409
|
+
return left.order - right.order;
|
|
410
|
+
})
|
|
411
|
+
);
|
|
412
|
+
chunk = [];
|
|
413
|
+
};
|
|
414
|
+
for (const record of records.sort((left, right) => left.order - right.order)) {
|
|
415
|
+
if (record.sideEffect) {
|
|
416
|
+
flushChunk();
|
|
417
|
+
sorted.push(record);
|
|
418
|
+
continue;
|
|
247
419
|
}
|
|
248
|
-
|
|
249
|
-
}
|
|
420
|
+
chunk.push(record);
|
|
421
|
+
}
|
|
422
|
+
flushChunk();
|
|
423
|
+
return sorted;
|
|
250
424
|
}
|
|
251
|
-
function classifyImport(source) {
|
|
252
|
-
if (source.startsWith(
|
|
425
|
+
function classifyImport(source, importAliases) {
|
|
426
|
+
if (importAliases.some((alias) => source.startsWith(alias))) {
|
|
253
427
|
return "alias";
|
|
254
428
|
}
|
|
255
429
|
if (source.startsWith(".")) {
|
|
@@ -258,11 +432,23 @@ function classifyImport(source) {
|
|
|
258
432
|
return "package";
|
|
259
433
|
}
|
|
260
434
|
function renderImportBlock(records) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
435
|
+
if (records.length === 0) {
|
|
436
|
+
return "";
|
|
437
|
+
}
|
|
438
|
+
const rendered = [];
|
|
439
|
+
records.forEach((record, index) => {
|
|
440
|
+
const previous = records[index - 1];
|
|
441
|
+
if (previous && shouldSeparateImports(previous, record)) {
|
|
442
|
+
rendered.push("");
|
|
443
|
+
}
|
|
444
|
+
rendered.push(renderImportRecord(record));
|
|
445
|
+
});
|
|
446
|
+
return `${rendered.join("\n")}
|
|
264
447
|
|
|
265
|
-
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
function shouldSeparateImports(left, right) {
|
|
451
|
+
return left.sideEffect || right.sideEffect || left.group !== right.group;
|
|
266
452
|
}
|
|
267
453
|
function renderImportRecord(record) {
|
|
268
454
|
if (record.sideEffect) {
|
|
@@ -303,6 +489,27 @@ function replaceImports(source, spans, importBlock) {
|
|
|
303
489
|
magic.appendLeft(firstStart, importBlock);
|
|
304
490
|
return magic.toString().replace(/^\n+/u, "");
|
|
305
491
|
}
|
|
492
|
+
var visitorKeys2 = t2.VISITOR_KEYS;
|
|
493
|
+
function walkNode(node, enter, parent) {
|
|
494
|
+
enter(node, parent);
|
|
495
|
+
const keys = visitorKeys2[node.type] ?? [];
|
|
496
|
+
const record = node;
|
|
497
|
+
for (const key of keys) {
|
|
498
|
+
const value = record[key];
|
|
499
|
+
if (Array.isArray(value)) {
|
|
500
|
+
for (const child of value) {
|
|
501
|
+
if (isNode2(child)) {
|
|
502
|
+
walkNode(child, enter, node);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else if (isNode2(value)) {
|
|
506
|
+
walkNode(value, enter, node);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function isNode2(value) {
|
|
511
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
512
|
+
}
|
|
306
513
|
var displayClasses = /* @__PURE__ */ new Set([
|
|
307
514
|
"block",
|
|
308
515
|
"inline-block",
|
|
@@ -315,9 +522,42 @@ var displayClasses = /* @__PURE__ */ new Set([
|
|
|
315
522
|
"hidden"
|
|
316
523
|
]);
|
|
317
524
|
var positionClasses = /* @__PURE__ */ new Set(["static", "fixed", "absolute", "relative", "sticky"]);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
525
|
+
var variantOrder = [
|
|
526
|
+
"sm",
|
|
527
|
+
"md",
|
|
528
|
+
"lg",
|
|
529
|
+
"xl",
|
|
530
|
+
"2xl",
|
|
531
|
+
"dark",
|
|
532
|
+
"motion-safe",
|
|
533
|
+
"motion-reduce",
|
|
534
|
+
"portrait",
|
|
535
|
+
"landscape",
|
|
536
|
+
"first",
|
|
537
|
+
"last",
|
|
538
|
+
"odd",
|
|
539
|
+
"even",
|
|
540
|
+
"visited",
|
|
541
|
+
"checked",
|
|
542
|
+
"empty",
|
|
543
|
+
"enabled",
|
|
544
|
+
"disabled",
|
|
545
|
+
"group-hover",
|
|
546
|
+
"group-focus",
|
|
547
|
+
"peer-hover",
|
|
548
|
+
"peer-focus",
|
|
549
|
+
"hover",
|
|
550
|
+
"focus",
|
|
551
|
+
"focus-within",
|
|
552
|
+
"focus-visible",
|
|
553
|
+
"active",
|
|
554
|
+
"invalid",
|
|
555
|
+
"placeholder-shown"
|
|
556
|
+
];
|
|
557
|
+
function normalizeClassList(value, options) {
|
|
558
|
+
const parsed = splitClasses(value);
|
|
559
|
+
const deduped = options.removeDuplicates ? [...new Set(parsed)] : parsed;
|
|
560
|
+
const classes = options.sortClasses ? [...deduped].sort(compareTailwindClasses) : deduped;
|
|
321
561
|
return classes.join(" ");
|
|
322
562
|
}
|
|
323
563
|
function splitClasses(value) {
|
|
@@ -326,7 +566,7 @@ function splitClasses(value) {
|
|
|
326
566
|
function compareTailwindClasses(left, right) {
|
|
327
567
|
const leftClass = classifyClass(left);
|
|
328
568
|
const rightClass = classifyClass(right);
|
|
329
|
-
const variantDelta = leftClass.variant
|
|
569
|
+
const variantDelta = compareVariants(leftClass.variant, rightClass.variant);
|
|
330
570
|
if (variantDelta !== 0) {
|
|
331
571
|
return variantDelta;
|
|
332
572
|
}
|
|
@@ -346,16 +586,52 @@ function conflictKey(className) {
|
|
|
346
586
|
if (positionClasses.has(utility)) {
|
|
347
587
|
return `${variantPrefix}position`;
|
|
348
588
|
}
|
|
589
|
+
if (/^-?z-/u.test(utility)) {
|
|
590
|
+
return `${variantPrefix}z-index`;
|
|
591
|
+
}
|
|
592
|
+
if (utility.startsWith("overflow-")) {
|
|
593
|
+
return `${variantPrefix}${utility.startsWith("overflow-x-") ? "overflow-x" : utility.startsWith("overflow-y-") ? "overflow-y" : "overflow"}`;
|
|
594
|
+
}
|
|
349
595
|
const spacing = spacingConflictKey(utility);
|
|
350
596
|
if (spacing) {
|
|
351
597
|
return `${variantPrefix}${spacing}`;
|
|
352
598
|
}
|
|
599
|
+
if (/^(w|h|min-w|min-h|max-w|max-h)-/u.test(utility)) {
|
|
600
|
+
return `${variantPrefix}${utility.split("-").slice(0, utility.startsWith("min-") || utility.startsWith("max-") ? 2 : 1).join("-")}`;
|
|
601
|
+
}
|
|
353
602
|
if (/^text-(xs|sm|base|lg|xl|[2-9]xl)$/u.test(utility)) {
|
|
354
603
|
return `${variantPrefix}font-size`;
|
|
355
604
|
}
|
|
605
|
+
if (/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/u.test(utility)) {
|
|
606
|
+
return `${variantPrefix}font-weight`;
|
|
607
|
+
}
|
|
608
|
+
if (/^leading-/u.test(utility)) {
|
|
609
|
+
return `${variantPrefix}line-height`;
|
|
610
|
+
}
|
|
356
611
|
if (utility.startsWith("bg-")) {
|
|
357
612
|
return `${variantPrefix}background-color`;
|
|
358
613
|
}
|
|
614
|
+
if (utility.startsWith("rounded")) {
|
|
615
|
+
return `${variantPrefix}${roundedConflictKey(utility)}`;
|
|
616
|
+
}
|
|
617
|
+
if (/^border(?:-[trblxy])?-/u.test(utility) || utility === "border") {
|
|
618
|
+
return `${variantPrefix}${borderConflictKey(utility)}`;
|
|
619
|
+
}
|
|
620
|
+
if (/^flex-(row|row-reverse|col|col-reverse)$/u.test(utility)) {
|
|
621
|
+
return `${variantPrefix}flex-direction`;
|
|
622
|
+
}
|
|
623
|
+
if (/^flex-(wrap|wrap-reverse|nowrap)$/u.test(utility)) {
|
|
624
|
+
return `${variantPrefix}flex-wrap`;
|
|
625
|
+
}
|
|
626
|
+
if (/^grid-cols-/u.test(utility)) {
|
|
627
|
+
return `${variantPrefix}grid-template-columns`;
|
|
628
|
+
}
|
|
629
|
+
if (/^grid-rows-/u.test(utility)) {
|
|
630
|
+
return `${variantPrefix}grid-template-rows`;
|
|
631
|
+
}
|
|
632
|
+
if (/^gap[xy]?-/u.test(utility)) {
|
|
633
|
+
return `${variantPrefix}${utility.startsWith("gap-x-") ? "gap-x" : utility.startsWith("gap-y-") ? "gap-y" : "gap"}`;
|
|
634
|
+
}
|
|
359
635
|
return void 0;
|
|
360
636
|
}
|
|
361
637
|
function spacingConflictKey(utility) {
|
|
@@ -396,6 +672,39 @@ function splitVariant(className) {
|
|
|
396
672
|
function stripImportant(utility) {
|
|
397
673
|
return utility.startsWith("!") ? utility.slice(1) : utility;
|
|
398
674
|
}
|
|
675
|
+
function compareVariants(left, right) {
|
|
676
|
+
const leftParts = left ? left.split(":") : [];
|
|
677
|
+
const rightParts = right ? right.split(":") : [];
|
|
678
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
679
|
+
for (let index = 0; index < length; index += 1) {
|
|
680
|
+
const leftPart = leftParts[index] ?? "";
|
|
681
|
+
const rightPart = rightParts[index] ?? "";
|
|
682
|
+
const delta = variantRank(leftPart) - variantRank(rightPart);
|
|
683
|
+
if (delta !== 0) {
|
|
684
|
+
return delta;
|
|
685
|
+
}
|
|
686
|
+
const lexical = leftPart.localeCompare(rightPart);
|
|
687
|
+
if (lexical !== 0) {
|
|
688
|
+
return lexical;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
693
|
+
function variantRank(variant) {
|
|
694
|
+
if (!variant) {
|
|
695
|
+
return -1;
|
|
696
|
+
}
|
|
697
|
+
const index = variantOrder.indexOf(variant);
|
|
698
|
+
return index >= 0 ? index : 1e3;
|
|
699
|
+
}
|
|
700
|
+
function roundedConflictKey(utility) {
|
|
701
|
+
const match = /^rounded(?:-(?<side>[trbl]|tl|tr|br|bl))?-/u.exec(utility);
|
|
702
|
+
return match?.groups?.side ? `border-radius-${match.groups.side}` : "border-radius";
|
|
703
|
+
}
|
|
704
|
+
function borderConflictKey(utility) {
|
|
705
|
+
const match = /^border(?:-(?<side>[trblxy]))?(?:-|$)/u.exec(utility);
|
|
706
|
+
return match?.groups?.side ? `border-${match.groups.side}` : "border";
|
|
707
|
+
}
|
|
399
708
|
function rankUtility(utility) {
|
|
400
709
|
if (utility.startsWith("container")) return 0;
|
|
401
710
|
if (positionClasses.has(utility) || utility.startsWith("inset-")) return 10;
|
|
@@ -436,11 +745,14 @@ var TailwindCleaner = class {
|
|
|
436
745
|
/** Returns source text with duplicated and unordered Tailwind classes normalized. */
|
|
437
746
|
static fix(context) {
|
|
438
747
|
const ast = parseSource(context.source);
|
|
439
|
-
const segments = collectClassSegments(ast);
|
|
748
|
+
const segments = collectClassSegments(ast, context.config.tailwindFunctions);
|
|
440
749
|
const magic = new MagicString2(context.source);
|
|
441
750
|
const conflicts = [];
|
|
442
751
|
for (const segment of segments) {
|
|
443
|
-
const nextValue = normalizeClassList(segment.value,
|
|
752
|
+
const nextValue = normalizeClassList(segment.value, {
|
|
753
|
+
removeDuplicates: context.config.removeDuplicateClasses,
|
|
754
|
+
sortClasses: context.config.sortTailwindClasses
|
|
755
|
+
});
|
|
444
756
|
if (nextValue !== segment.value) {
|
|
445
757
|
magic.overwrite(segment.start, segment.end, nextValue);
|
|
446
758
|
}
|
|
@@ -462,8 +774,9 @@ var TailwindCleaner = class {
|
|
|
462
774
|
};
|
|
463
775
|
}
|
|
464
776
|
};
|
|
465
|
-
function collectClassSegments(ast) {
|
|
777
|
+
function collectClassSegments(ast, tailwindFunctions) {
|
|
466
778
|
const segments = [];
|
|
779
|
+
const functionNames = new Set(tailwindFunctions);
|
|
467
780
|
walkNode(ast, (node) => {
|
|
468
781
|
if (t3.isJSXAttribute(node)) {
|
|
469
782
|
if (!isClassAttribute(node)) {
|
|
@@ -480,6 +793,8 @@ function collectClassSegments(ast) {
|
|
|
480
793
|
segments.push(segmentFromTemplateElement(quasi));
|
|
481
794
|
}
|
|
482
795
|
}
|
|
796
|
+
} else if (t3.isCallExpression(node) && isTailwindFunctionCall(node, functionNames)) {
|
|
797
|
+
collectCallExpressionSegments(node, segments);
|
|
483
798
|
}
|
|
484
799
|
});
|
|
485
800
|
return segments;
|
|
@@ -507,6 +822,74 @@ function segmentFromTemplateElement(node) {
|
|
|
507
822
|
line: node.loc?.start.line ?? 1
|
|
508
823
|
};
|
|
509
824
|
}
|
|
825
|
+
function collectCallExpressionSegments(node, segments) {
|
|
826
|
+
for (const argument of node.arguments) {
|
|
827
|
+
collectExpressionSegments(argument, segments);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function collectExpressionSegments(node, segments) {
|
|
831
|
+
if (t3.isStringLiteral(node)) {
|
|
832
|
+
segments.push(segmentFromStringLiteral(node));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (t3.isTemplateLiteral(node) && node.expressions.length === 0) {
|
|
836
|
+
const quasi = node.quasis[0];
|
|
837
|
+
if (quasi) {
|
|
838
|
+
segments.push(segmentFromTemplateElement(quasi));
|
|
839
|
+
}
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (t3.isArrayExpression(node)) {
|
|
843
|
+
for (const element of node.elements) {
|
|
844
|
+
if (element) {
|
|
845
|
+
collectExpressionSegments(element, segments);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (t3.isObjectExpression(node)) {
|
|
851
|
+
for (const property of node.properties) {
|
|
852
|
+
if (t3.isObjectProperty(property)) {
|
|
853
|
+
collectObjectPropertySegments(property, segments);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (t3.isConditionalExpression(node)) {
|
|
859
|
+
collectExpressionSegments(node.consequent, segments);
|
|
860
|
+
collectExpressionSegments(node.alternate, segments);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (t3.isLogicalExpression(node)) {
|
|
864
|
+
collectExpressionSegments(node.right, segments);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function collectObjectPropertySegments(node, segments) {
|
|
868
|
+
if (t3.isStringLiteral(node.key)) {
|
|
869
|
+
segments.push(segmentFromStringLiteral(node.key));
|
|
870
|
+
} else if (t3.isTemplateLiteral(node.key) && node.key.expressions.length === 0) {
|
|
871
|
+
const quasi = node.key.quasis[0];
|
|
872
|
+
if (quasi) {
|
|
873
|
+
segments.push(segmentFromTemplateElement(quasi));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (t3.isExpression(node.value)) {
|
|
877
|
+
collectExpressionSegments(node.value, segments);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function isTailwindFunctionCall(node, functionNames) {
|
|
881
|
+
const name = calleeName(node.callee);
|
|
882
|
+
return name ? functionNames.has(name) : false;
|
|
883
|
+
}
|
|
884
|
+
function calleeName(node) {
|
|
885
|
+
if (t3.isIdentifier(node)) {
|
|
886
|
+
return node.name;
|
|
887
|
+
}
|
|
888
|
+
if (t3.isMemberExpression(node) && t3.isIdentifier(node.property) && !node.computed) {
|
|
889
|
+
return node.property.name;
|
|
890
|
+
}
|
|
891
|
+
return void 0;
|
|
892
|
+
}
|
|
510
893
|
function detectConflicts(file, line, classes) {
|
|
511
894
|
const groups = /* @__PURE__ */ new Map();
|
|
512
895
|
for (const className of classes) {
|
|
@@ -612,25 +995,83 @@ async function processProjectFiles(options, mode) {
|
|
|
612
995
|
const result = runCleaner(ImportCleaner, mode, { filePath, source: output, config });
|
|
613
996
|
issues.push(...result.issues);
|
|
614
997
|
output = result.output ?? output;
|
|
998
|
+
if (hasParseIssue(result.issues)) {
|
|
999
|
+
processed.push({ filePath, source, output, issues, conflicts });
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
615
1002
|
}
|
|
616
1003
|
if (config.tailwind) {
|
|
617
1004
|
const result = runCleaner(TailwindCleaner, mode, { filePath, source: output, config });
|
|
618
1005
|
issues.push(...result.issues);
|
|
619
1006
|
conflicts.push(...result.conflicts ?? []);
|
|
620
1007
|
output = result.output ?? output;
|
|
1008
|
+
if (hasParseIssue(result.issues)) {
|
|
1009
|
+
processed.push({ filePath, source, output, issues, conflicts });
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (shouldFormat(config, options)) {
|
|
1014
|
+
const result = await formatWithPrettier(filePath, output);
|
|
1015
|
+
issues.push(...result.issues);
|
|
1016
|
+
output = result.output;
|
|
621
1017
|
}
|
|
622
1018
|
processed.push({ filePath, source, output, issues, conflicts });
|
|
623
1019
|
}
|
|
624
1020
|
return processed;
|
|
625
1021
|
}
|
|
626
1022
|
function runCleaner(cleaner, mode, context) {
|
|
627
|
-
|
|
1023
|
+
try {
|
|
1024
|
+
return mode === "check" ? cleaner.check(context) : cleaner.fix(context);
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
return {
|
|
1027
|
+
output: context.source,
|
|
1028
|
+
issues: [
|
|
1029
|
+
{
|
|
1030
|
+
file: context.filePath,
|
|
1031
|
+
line: 1,
|
|
1032
|
+
kind: "parse",
|
|
1033
|
+
message: `Unable to parse file: ${errorMessage(error)}`
|
|
1034
|
+
}
|
|
1035
|
+
]
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function shouldFormat(config, options) {
|
|
1040
|
+
return options.format === true || config.format === "prettier";
|
|
1041
|
+
}
|
|
1042
|
+
async function formatWithPrettier(filePath, source) {
|
|
1043
|
+
try {
|
|
1044
|
+
const resolvedConfig = await prettier.resolveConfig(filePath);
|
|
1045
|
+
const output = await prettier.format(source, {
|
|
1046
|
+
...resolvedConfig,
|
|
1047
|
+
filepath: filePath
|
|
1048
|
+
});
|
|
1049
|
+
return { output, issues: [] };
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
return {
|
|
1052
|
+
output: source,
|
|
1053
|
+
issues: [
|
|
1054
|
+
{
|
|
1055
|
+
file: filePath,
|
|
1056
|
+
line: 1,
|
|
1057
|
+
kind: "format",
|
|
1058
|
+
message: `Unable to format with Prettier: ${errorMessage(error)}`
|
|
1059
|
+
}
|
|
1060
|
+
]
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function errorMessage(error) {
|
|
1065
|
+
return error instanceof Error ? error.message : String(error);
|
|
1066
|
+
}
|
|
1067
|
+
function hasParseIssue(issues) {
|
|
1068
|
+
return issues.some((issue) => issue.kind === "parse");
|
|
628
1069
|
}
|
|
629
1070
|
|
|
630
1071
|
// src/index.ts
|
|
631
1072
|
var program = new Command();
|
|
632
|
-
program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.
|
|
633
|
-
program.command("check").description("Check files without writing changes.").option("--cwd <path>", "Working directory").option("--config <path>", "Path to cleanwind config").option("--write", "Write fixes while running check").option("--check", "Force check mode", true).option("--verbose", "Print detailed diagnostics").action(async (options) => {
|
|
1073
|
+
program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.3.0");
|
|
1074
|
+
program.command("check").description("Check files without writing changes.").option("--cwd <path>", "Working directory").option("--config <path>", "Path to cleanwind config").option("--write", "Write fixes while running check").option("--check", "Force check mode", true).option("--verbose", "Print detailed diagnostics").option("--format", "Format output with Prettier after cleanup").action(async (options) => {
|
|
634
1075
|
const runOptions = toRunOptions(options);
|
|
635
1076
|
if (options.write) {
|
|
636
1077
|
const result2 = await fix({ ...runOptions, write: true });
|
|
@@ -642,7 +1083,7 @@ program.command("check").description("Check files without writing changes.").opt
|
|
|
642
1083
|
printCheckResult(result, options.verbose ?? false);
|
|
643
1084
|
process.exitCode = result.ok ? 0 : 1;
|
|
644
1085
|
});
|
|
645
|
-
program.command("fix").description("Fix files in place.").option("--cwd <path>", "Working directory").option("--config <path>", "Path to cleanwind config").option("--write", "Write fixes", true).option("--check", "Preview fixes without writing").option("--verbose", "Print detailed diagnostics").option("--staged", "Only fix staged files").action(async (options) => {
|
|
1086
|
+
program.command("fix").description("Fix files in place.").option("--cwd <path>", "Working directory").option("--config <path>", "Path to cleanwind config").option("--write", "Write fixes", true).option("--check", "Preview fixes without writing").option("--verbose", "Print detailed diagnostics").option("--staged", "Only fix staged files").option("--format", "Format output with Prettier after cleanup").action(async (options) => {
|
|
646
1087
|
const result = await fix({
|
|
647
1088
|
...toRunOptions(options),
|
|
648
1089
|
write: options.check ? false : options.write ?? true,
|
|
@@ -660,6 +1101,7 @@ function toRunOptions(options) {
|
|
|
660
1101
|
if (options.check !== void 0) runOptions.check = options.check;
|
|
661
1102
|
if (options.verbose !== void 0) runOptions.verbose = options.verbose;
|
|
662
1103
|
if (options.staged !== void 0) runOptions.staged = options.staged;
|
|
1104
|
+
if (options.format !== void 0) runOptions.format = options.format;
|
|
663
1105
|
return runOptions;
|
|
664
1106
|
}
|
|
665
1107
|
function printCheckResult(result, verbose) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cleanwind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Clean imports and Tailwind CSS class names from the command line.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"commander": "^12.1.0",
|
|
41
41
|
"fast-glob": "^3.3.2",
|
|
42
42
|
"jiti": "^2.4.2",
|
|
43
|
-
"magic-string": "^0.30.17"
|
|
43
|
+
"magic-string": "^0.30.17",
|
|
44
|
+
"prettier": "^3.4.2"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@cleanwind/core": "workspace:*"
|