cleanwind 0.1.3 → 0.3.1
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 +665 -108
- 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
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import path4 from "path";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// ../core/dist/index.js
|
|
7
8
|
import { existsSync } from "fs";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import { createJiti } from "jiti";
|
|
10
|
-
import * as
|
|
11
|
+
import * as t from "@babel/types";
|
|
11
12
|
import MagicString from "magic-string";
|
|
12
13
|
import { parse } from "@babel/parser";
|
|
13
|
-
import * as t from "@babel/types";
|
|
14
14
|
import * as t3 from "@babel/types";
|
|
15
15
|
import MagicString2 from "magic-string";
|
|
16
|
+
import * as t2 from "@babel/types";
|
|
16
17
|
import { promises as fs } from "fs";
|
|
17
18
|
import path3 from "path";
|
|
19
|
+
import * as prettier from "prettier";
|
|
18
20
|
import { execFileSync } from "child_process";
|
|
19
21
|
import path2 from "path";
|
|
20
22
|
import fg from "fast-glob";
|
|
@@ -22,10 +24,14 @@ var defaultConfig = {
|
|
|
22
24
|
imports: true,
|
|
23
25
|
tailwind: true,
|
|
24
26
|
removeDuplicateImports: true,
|
|
27
|
+
removeUnusedImports: false,
|
|
25
28
|
removeDuplicateClasses: true,
|
|
26
29
|
sortImports: true,
|
|
30
|
+
importAliases: ["@/"],
|
|
27
31
|
sortTailwindClasses: true,
|
|
32
|
+
tailwindFunctions: ["clsx", "classnames", "cva", "cn", "twMerge"],
|
|
28
33
|
detectConflicts: true,
|
|
34
|
+
format: false,
|
|
29
35
|
include: ["src/**/*.{js,jsx,ts,tsx}"],
|
|
30
36
|
exclude: ["node_modules", ".next", "dist"]
|
|
31
37
|
};
|
|
@@ -77,26 +83,6 @@ function parseSource(source) {
|
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
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
86
|
var ImportCleaner = class {
|
|
101
87
|
/** Checks whether imports would change after cleanwind normalization. */
|
|
102
88
|
static check(context) {
|
|
@@ -115,105 +101,277 @@ var ImportCleaner = class {
|
|
|
115
101
|
/** Returns source text with unused, duplicated, and unordered imports normalized. */
|
|
116
102
|
static fix(context) {
|
|
117
103
|
const ast = parseSource(context.source);
|
|
118
|
-
const imports = ast.program.body.filter(
|
|
104
|
+
const imports = ast.program.body.filter(t.isImportDeclaration);
|
|
119
105
|
if (imports.length === 0) {
|
|
120
106
|
return { output: context.source, issues: [] };
|
|
121
107
|
}
|
|
122
|
-
const usedIdentifiers =
|
|
123
|
-
const records = buildImportRecords(imports, usedIdentifiers,
|
|
108
|
+
const usedIdentifiers = context.config.removeUnusedImports ? collectUsedImportIdentifiers(ast) : void 0;
|
|
109
|
+
const records = buildImportRecords(imports, usedIdentifiers, {
|
|
110
|
+
mergeDuplicates: context.config.removeDuplicateImports,
|
|
111
|
+
sortImports: context.config.sortImports,
|
|
112
|
+
importAliases: context.config.importAliases
|
|
113
|
+
});
|
|
124
114
|
const spans = imports.map((declaration) => spanForImport(context.source, declaration));
|
|
125
115
|
const output = replaceImports(context.source, spans, renderImportBlock(records));
|
|
126
116
|
return { output, issues: [] };
|
|
127
117
|
}
|
|
128
118
|
};
|
|
129
|
-
function
|
|
130
|
-
const
|
|
119
|
+
function collectUsedImportIdentifiers(ast) {
|
|
120
|
+
const used = /* @__PURE__ */ new Set();
|
|
121
|
+
const programScope = createScope();
|
|
131
122
|
for (const statement of ast.program.body) {
|
|
132
|
-
if (
|
|
123
|
+
if (!t.isImportDeclaration(statement)) {
|
|
133
124
|
continue;
|
|
134
125
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
if (t2.isJSXIdentifier(node) && isJSXIdentifierReference(parent, node)) {
|
|
140
|
-
identifiers.add(node.name);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
126
|
+
for (const specifier of statement.specifiers) {
|
|
127
|
+
programScope.bindings.set(specifier.local.name, "import");
|
|
128
|
+
}
|
|
143
129
|
}
|
|
144
|
-
|
|
130
|
+
for (const statement of ast.program.body) {
|
|
131
|
+
if (t.isImportDeclaration(statement)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
collectHoistedBindings(statement, programScope);
|
|
135
|
+
walkUsage(statement, void 0, [programScope], used);
|
|
136
|
+
}
|
|
137
|
+
return used;
|
|
145
138
|
}
|
|
146
139
|
function isIdentifierReference(parent, node) {
|
|
147
140
|
if (!parent) {
|
|
148
141
|
return true;
|
|
149
142
|
}
|
|
150
|
-
if (
|
|
151
|
-
if ((
|
|
143
|
+
if (t.isVariableDeclarator(parent) && parent.id === node) return false;
|
|
144
|
+
if ((t.isFunctionDeclaration(parent) || t.isFunctionExpression(parent)) && parent.id === node)
|
|
152
145
|
return false;
|
|
153
|
-
if ((
|
|
146
|
+
if ((t.isClassDeclaration(parent) || t.isClassExpression(parent)) && parent.id === node)
|
|
154
147
|
return false;
|
|
155
|
-
if (
|
|
156
|
-
if (
|
|
157
|
-
if (
|
|
158
|
-
if (
|
|
159
|
-
if (
|
|
160
|
-
if (
|
|
148
|
+
if (t.isObjectProperty(parent) && parent.key === node && !parent.computed) return false;
|
|
149
|
+
if (t.isObjectMethod(parent) && parent.key === node && !parent.computed) return false;
|
|
150
|
+
if (t.isMemberExpression(parent) && parent.property === node && !parent.computed) return false;
|
|
151
|
+
if (t.isLabeledStatement(parent) && parent.label === node) return false;
|
|
152
|
+
if (t.isTSTypeAliasDeclaration(parent) && parent.id === node) return false;
|
|
153
|
+
if (t.isTSInterfaceDeclaration(parent) && parent.id === node) return false;
|
|
161
154
|
return true;
|
|
162
155
|
}
|
|
163
156
|
function isJSXIdentifierReference(parent, node) {
|
|
164
157
|
if (!parent) {
|
|
165
158
|
return false;
|
|
166
159
|
}
|
|
167
|
-
if (
|
|
160
|
+
if (t.isJSXAttribute(parent)) {
|
|
168
161
|
return false;
|
|
169
162
|
}
|
|
170
|
-
return
|
|
163
|
+
return t.isJSXOpeningElement(parent) && parent.name === node || t.isJSXClosingElement(parent) && parent.name === node || t.isJSXMemberExpression(parent) && parent.object === node;
|
|
164
|
+
}
|
|
165
|
+
function walkUsage(node, parent, scopes, used) {
|
|
166
|
+
if (t.isImportDeclaration(node)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (t.isFunctionDeclaration(node)) {
|
|
170
|
+
declareIdentifier(node.id, currentScope(scopes));
|
|
171
|
+
const functionScope = createChildScope();
|
|
172
|
+
declarePatterns(node.params, functionScope);
|
|
173
|
+
if (node.body) {
|
|
174
|
+
walkUsage(node.body, node, [...scopes, functionScope], used);
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
|
|
179
|
+
const functionScope = createChildScope();
|
|
180
|
+
if (t.isFunctionExpression(node)) {
|
|
181
|
+
declareIdentifier(node.id, functionScope);
|
|
182
|
+
}
|
|
183
|
+
declarePatterns(node.params, functionScope);
|
|
184
|
+
walkChildren(node, parent, [...scopes, functionScope], used, /* @__PURE__ */ new Set(["id", "params"]));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (t.isBlockStatement(node) || t.isProgram(node)) {
|
|
188
|
+
const blockScope = t.isProgram(node) ? currentScope(scopes) : createChildScope();
|
|
189
|
+
collectHoistedBindings(node, blockScope);
|
|
190
|
+
const nextScopes = t.isProgram(node) ? scopes : [...scopes, blockScope];
|
|
191
|
+
walkChildren(node, parent, nextScopes, used);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (t.isVariableDeclarator(node)) {
|
|
195
|
+
declarePattern(node.id, currentScope(scopes));
|
|
196
|
+
if (node.init) {
|
|
197
|
+
walkUsage(node.init, node, scopes, used);
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (t.isClassDeclaration(node)) {
|
|
202
|
+
declareIdentifier(node.id, currentScope(scopes));
|
|
203
|
+
walkChildren(node, parent, scopes, used, /* @__PURE__ */ new Set(["id"]));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (t.isCatchClause(node)) {
|
|
207
|
+
const catchScope = createChildScope();
|
|
208
|
+
if (node.param) {
|
|
209
|
+
declarePattern(node.param, catchScope);
|
|
210
|
+
}
|
|
211
|
+
walkUsage(node.body, node, [...scopes, catchScope], used);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (t.isTSTypeAliasDeclaration(node) || t.isTSInterfaceDeclaration(node)) {
|
|
215
|
+
declareIdentifier(node.id, currentScope(scopes));
|
|
216
|
+
walkChildren(node, parent, scopes, used, /* @__PURE__ */ new Set(["id"]));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (t.isIdentifier(node) && isIdentifierReference(parent, node) && resolvesToImport(node.name, scopes)) {
|
|
220
|
+
used.add(node.name);
|
|
221
|
+
}
|
|
222
|
+
if (t.isJSXIdentifier(node) && isJSXIdentifierReference(parent, node) && resolvesToImport(node.name, scopes)) {
|
|
223
|
+
used.add(node.name);
|
|
224
|
+
}
|
|
225
|
+
walkChildren(node, parent, scopes, used);
|
|
226
|
+
}
|
|
227
|
+
function walkChildren(node, parent, scopes, used, skipKeys = /* @__PURE__ */ new Set()) {
|
|
228
|
+
const keys = visitorKeys[node.type] ?? [];
|
|
229
|
+
const record = node;
|
|
230
|
+
for (const key of keys) {
|
|
231
|
+
if (skipKeys.has(key)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const value = record[key];
|
|
235
|
+
if (Array.isArray(value)) {
|
|
236
|
+
for (const child of value) {
|
|
237
|
+
if (isNode(child)) {
|
|
238
|
+
walkUsage(child, node, scopes, used);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} else if (isNode(value)) {
|
|
242
|
+
walkUsage(value, node, scopes, used);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function collectHoistedBindings(node, scope) {
|
|
247
|
+
const body = t.isProgram(node) || t.isBlockStatement(node) ? node.body : [node];
|
|
248
|
+
for (const statement of body) {
|
|
249
|
+
if (t.isFunctionDeclaration(statement)) {
|
|
250
|
+
declareIdentifier(statement.id, scope);
|
|
251
|
+
} else if (t.isClassDeclaration(statement)) {
|
|
252
|
+
declareIdentifier(statement.id, scope);
|
|
253
|
+
} else if (t.isVariableDeclaration(statement)) {
|
|
254
|
+
for (const declaration of statement.declarations) {
|
|
255
|
+
declarePattern(declaration.id, scope);
|
|
256
|
+
}
|
|
257
|
+
} else if (t.isTSTypeAliasDeclaration(statement) || t.isTSInterfaceDeclaration(statement)) {
|
|
258
|
+
declareIdentifier(statement.id, scope);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function createScope() {
|
|
263
|
+
return { bindings: /* @__PURE__ */ new Map() };
|
|
264
|
+
}
|
|
265
|
+
function createChildScope() {
|
|
266
|
+
return createScope();
|
|
267
|
+
}
|
|
268
|
+
function currentScope(scopes) {
|
|
269
|
+
return scopes[scopes.length - 1] ?? createScope();
|
|
270
|
+
}
|
|
271
|
+
function declareIdentifier(node, scope) {
|
|
272
|
+
if (node) {
|
|
273
|
+
scope.bindings.set(node.name, "local");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function declarePatterns(patterns, scope) {
|
|
277
|
+
for (const pattern of patterns) {
|
|
278
|
+
declarePattern(pattern, scope);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function declarePattern(pattern, scope) {
|
|
282
|
+
if (t.isIdentifier(pattern)) {
|
|
283
|
+
scope.bindings.set(pattern.name, "local");
|
|
284
|
+
} else if (t.isTSParameterProperty(pattern)) {
|
|
285
|
+
declarePattern(pattern.parameter, scope);
|
|
286
|
+
} else if (t.isRestElement(pattern)) {
|
|
287
|
+
declarePattern(pattern.argument, scope);
|
|
288
|
+
} else if (t.isAssignmentPattern(pattern)) {
|
|
289
|
+
declarePattern(pattern.left, scope);
|
|
290
|
+
} else if (t.isObjectPattern(pattern)) {
|
|
291
|
+
for (const property of pattern.properties) {
|
|
292
|
+
if (t.isObjectProperty(property)) {
|
|
293
|
+
declarePattern(property.value, scope);
|
|
294
|
+
} else if (t.isRestElement(property)) {
|
|
295
|
+
declarePattern(property.argument, scope);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (t.isArrayPattern(pattern)) {
|
|
299
|
+
for (const element of pattern.elements) {
|
|
300
|
+
if (element) {
|
|
301
|
+
declarePattern(element, scope);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function resolvesToImport(name, scopes) {
|
|
307
|
+
for (let index = scopes.length - 1; index >= 0; index -= 1) {
|
|
308
|
+
const binding = scopes[index]?.bindings.get(name);
|
|
309
|
+
if (binding) {
|
|
310
|
+
return binding === "import";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
171
314
|
}
|
|
172
|
-
function
|
|
315
|
+
function isNode(value) {
|
|
316
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
317
|
+
}
|
|
318
|
+
function buildImportRecords(declarations, usedIdentifiers, options) {
|
|
173
319
|
const records = /* @__PURE__ */ new Map();
|
|
174
|
-
const
|
|
175
|
-
|
|
320
|
+
const orderedRecords = [];
|
|
321
|
+
let sideEffectChunk = 0;
|
|
322
|
+
declarations.forEach((declaration, order) => {
|
|
176
323
|
const source = declaration.source.value;
|
|
177
324
|
const sideEffect = declaration.specifiers.length === 0;
|
|
178
325
|
if (sideEffect) {
|
|
179
|
-
|
|
326
|
+
orderedRecords.push({
|
|
327
|
+
key: `side-effect:${order}`,
|
|
180
328
|
source,
|
|
181
329
|
named: [],
|
|
182
330
|
sideEffect: true,
|
|
183
|
-
group: classifyImport(source)
|
|
331
|
+
group: classifyImport(source, options.importAliases),
|
|
332
|
+
order
|
|
184
333
|
});
|
|
185
|
-
|
|
334
|
+
sideEffectChunk += 1;
|
|
335
|
+
return;
|
|
186
336
|
}
|
|
187
|
-
const key = source
|
|
337
|
+
const key = options.mergeDuplicates ? `${sideEffectChunk}:${source}` : `${order}:${source}`;
|
|
188
338
|
const record = records.get(key) ?? {
|
|
189
339
|
source,
|
|
340
|
+
key,
|
|
190
341
|
named: [],
|
|
191
342
|
sideEffect: false,
|
|
192
|
-
group: classifyImport(source)
|
|
343
|
+
group: classifyImport(source, options.importAliases),
|
|
344
|
+
order
|
|
193
345
|
};
|
|
194
346
|
for (const specifier of declaration.specifiers) {
|
|
195
347
|
const local = specifier.local.name;
|
|
196
|
-
if (!usedIdentifiers.has(local)) {
|
|
348
|
+
if (usedIdentifiers && !usedIdentifiers.has(local)) {
|
|
197
349
|
continue;
|
|
198
350
|
}
|
|
199
|
-
if (
|
|
351
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
200
352
|
record.defaultName ??= local;
|
|
201
|
-
} else if (
|
|
353
|
+
} else if (t.isImportNamespaceSpecifier(specifier)) {
|
|
202
354
|
record.namespaceName ??= local;
|
|
203
|
-
} else if (
|
|
355
|
+
} else if (t.isImportSpecifier(specifier)) {
|
|
204
356
|
const declarationKind = declaration.importKind === "type" ? "type" : "value";
|
|
205
357
|
record.named.push(importPartFromSpecifier(specifier, declarationKind));
|
|
206
358
|
}
|
|
207
359
|
}
|
|
208
360
|
if (record.defaultName || record.namespaceName || record.named.length > 0) {
|
|
209
|
-
|
|
361
|
+
const nextRecord = options.mergeDuplicates ? dedupeRecord(record) : record;
|
|
362
|
+
records.set(key, nextRecord);
|
|
363
|
+
const existingIndex = orderedRecords.findIndex((item) => item.key === key);
|
|
364
|
+
if (existingIndex >= 0) {
|
|
365
|
+
orderedRecords[existingIndex] = nextRecord;
|
|
366
|
+
} else {
|
|
367
|
+
orderedRecords.push(nextRecord);
|
|
368
|
+
}
|
|
210
369
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return sort ? sortRecords(merged) : merged;
|
|
370
|
+
});
|
|
371
|
+
return options.sortImports ? sortRecords(orderedRecords) : orderedRecords;
|
|
214
372
|
}
|
|
215
373
|
function importPartFromSpecifier(specifier, declarationKind) {
|
|
216
|
-
const imported =
|
|
374
|
+
const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
|
|
217
375
|
return {
|
|
218
376
|
imported,
|
|
219
377
|
local: specifier.local.name,
|
|
@@ -236,20 +394,37 @@ function sortRecords(records) {
|
|
|
236
394
|
alias: 1,
|
|
237
395
|
relative: 2
|
|
238
396
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
397
|
+
const sorted = [];
|
|
398
|
+
let chunk = [];
|
|
399
|
+
const flushChunk = () => {
|
|
400
|
+
sorted.push(
|
|
401
|
+
...chunk.sort((left, right) => {
|
|
402
|
+
const groupDelta = groupRank[left.group] - groupRank[right.group];
|
|
403
|
+
if (groupDelta !== 0) {
|
|
404
|
+
return groupDelta;
|
|
405
|
+
}
|
|
406
|
+
const sourceDelta = left.source.localeCompare(right.source);
|
|
407
|
+
if (sourceDelta !== 0) {
|
|
408
|
+
return sourceDelta;
|
|
409
|
+
}
|
|
410
|
+
return left.order - right.order;
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
chunk = [];
|
|
414
|
+
};
|
|
415
|
+
for (const record of records.sort((left, right) => left.order - right.order)) {
|
|
416
|
+
if (record.sideEffect) {
|
|
417
|
+
flushChunk();
|
|
418
|
+
sorted.push(record);
|
|
419
|
+
continue;
|
|
247
420
|
}
|
|
248
|
-
|
|
249
|
-
}
|
|
421
|
+
chunk.push(record);
|
|
422
|
+
}
|
|
423
|
+
flushChunk();
|
|
424
|
+
return sorted;
|
|
250
425
|
}
|
|
251
|
-
function classifyImport(source) {
|
|
252
|
-
if (source.startsWith(
|
|
426
|
+
function classifyImport(source, importAliases) {
|
|
427
|
+
if (importAliases.some((alias) => source.startsWith(alias))) {
|
|
253
428
|
return "alias";
|
|
254
429
|
}
|
|
255
430
|
if (source.startsWith(".")) {
|
|
@@ -258,11 +433,23 @@ function classifyImport(source) {
|
|
|
258
433
|
return "package";
|
|
259
434
|
}
|
|
260
435
|
function renderImportBlock(records) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
436
|
+
if (records.length === 0) {
|
|
437
|
+
return "";
|
|
438
|
+
}
|
|
439
|
+
const rendered = [];
|
|
440
|
+
records.forEach((record, index) => {
|
|
441
|
+
const previous = records[index - 1];
|
|
442
|
+
if (previous && shouldSeparateImports(previous, record)) {
|
|
443
|
+
rendered.push("");
|
|
444
|
+
}
|
|
445
|
+
rendered.push(renderImportRecord(record));
|
|
446
|
+
});
|
|
447
|
+
return `${rendered.join("\n")}
|
|
264
448
|
|
|
265
|
-
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
function shouldSeparateImports(left, right) {
|
|
452
|
+
return left.sideEffect || right.sideEffect || left.group !== right.group;
|
|
266
453
|
}
|
|
267
454
|
function renderImportRecord(record) {
|
|
268
455
|
if (record.sideEffect) {
|
|
@@ -303,6 +490,27 @@ function replaceImports(source, spans, importBlock) {
|
|
|
303
490
|
magic.appendLeft(firstStart, importBlock);
|
|
304
491
|
return magic.toString().replace(/^\n+/u, "");
|
|
305
492
|
}
|
|
493
|
+
var visitorKeys2 = t2.VISITOR_KEYS;
|
|
494
|
+
function walkNode(node, enter, parent) {
|
|
495
|
+
enter(node, parent);
|
|
496
|
+
const keys = visitorKeys2[node.type] ?? [];
|
|
497
|
+
const record = node;
|
|
498
|
+
for (const key of keys) {
|
|
499
|
+
const value = record[key];
|
|
500
|
+
if (Array.isArray(value)) {
|
|
501
|
+
for (const child of value) {
|
|
502
|
+
if (isNode2(child)) {
|
|
503
|
+
walkNode(child, enter, node);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} else if (isNode2(value)) {
|
|
507
|
+
walkNode(value, enter, node);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function isNode2(value) {
|
|
512
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
513
|
+
}
|
|
306
514
|
var displayClasses = /* @__PURE__ */ new Set([
|
|
307
515
|
"block",
|
|
308
516
|
"inline-block",
|
|
@@ -315,9 +523,43 @@ var displayClasses = /* @__PURE__ */ new Set([
|
|
|
315
523
|
"hidden"
|
|
316
524
|
]);
|
|
317
525
|
var positionClasses = /* @__PURE__ */ new Set(["static", "fixed", "absolute", "relative", "sticky"]);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
526
|
+
var borderStyleClasses = /* @__PURE__ */ new Set(["solid", "dashed", "dotted", "double", "hidden", "none"]);
|
|
527
|
+
var variantOrder = [
|
|
528
|
+
"sm",
|
|
529
|
+
"md",
|
|
530
|
+
"lg",
|
|
531
|
+
"xl",
|
|
532
|
+
"2xl",
|
|
533
|
+
"dark",
|
|
534
|
+
"motion-safe",
|
|
535
|
+
"motion-reduce",
|
|
536
|
+
"portrait",
|
|
537
|
+
"landscape",
|
|
538
|
+
"first",
|
|
539
|
+
"last",
|
|
540
|
+
"odd",
|
|
541
|
+
"even",
|
|
542
|
+
"visited",
|
|
543
|
+
"checked",
|
|
544
|
+
"empty",
|
|
545
|
+
"enabled",
|
|
546
|
+
"disabled",
|
|
547
|
+
"group-hover",
|
|
548
|
+
"group-focus",
|
|
549
|
+
"peer-hover",
|
|
550
|
+
"peer-focus",
|
|
551
|
+
"hover",
|
|
552
|
+
"focus",
|
|
553
|
+
"focus-within",
|
|
554
|
+
"focus-visible",
|
|
555
|
+
"active",
|
|
556
|
+
"invalid",
|
|
557
|
+
"placeholder-shown"
|
|
558
|
+
];
|
|
559
|
+
function normalizeClassList(value, options) {
|
|
560
|
+
const parsed = splitClasses(value);
|
|
561
|
+
const deduped = options.removeDuplicates ? [...new Set(parsed)] : parsed;
|
|
562
|
+
const classes = options.sortClasses ? [...deduped].sort(compareTailwindClasses) : deduped;
|
|
321
563
|
return classes.join(" ");
|
|
322
564
|
}
|
|
323
565
|
function splitClasses(value) {
|
|
@@ -326,7 +568,7 @@ function splitClasses(value) {
|
|
|
326
568
|
function compareTailwindClasses(left, right) {
|
|
327
569
|
const leftClass = classifyClass(left);
|
|
328
570
|
const rightClass = classifyClass(right);
|
|
329
|
-
const variantDelta = leftClass.variant
|
|
571
|
+
const variantDelta = compareVariants(leftClass.variant, rightClass.variant);
|
|
330
572
|
if (variantDelta !== 0) {
|
|
331
573
|
return variantDelta;
|
|
332
574
|
}
|
|
@@ -346,16 +588,53 @@ function conflictKey(className) {
|
|
|
346
588
|
if (positionClasses.has(utility)) {
|
|
347
589
|
return `${variantPrefix}position`;
|
|
348
590
|
}
|
|
591
|
+
if (/^-?z-/u.test(utility)) {
|
|
592
|
+
return `${variantPrefix}z-index`;
|
|
593
|
+
}
|
|
594
|
+
if (utility.startsWith("overflow-")) {
|
|
595
|
+
return `${variantPrefix}${utility.startsWith("overflow-x-") ? "overflow-x" : utility.startsWith("overflow-y-") ? "overflow-y" : "overflow"}`;
|
|
596
|
+
}
|
|
349
597
|
const spacing = spacingConflictKey(utility);
|
|
350
598
|
if (spacing) {
|
|
351
599
|
return `${variantPrefix}${spacing}`;
|
|
352
600
|
}
|
|
601
|
+
if (/^(w|h|min-w|min-h|max-w|max-h)-/u.test(utility)) {
|
|
602
|
+
return `${variantPrefix}${utility.split("-").slice(0, utility.startsWith("min-") || utility.startsWith("max-") ? 2 : 1).join("-")}`;
|
|
603
|
+
}
|
|
353
604
|
if (/^text-(xs|sm|base|lg|xl|[2-9]xl)$/u.test(utility)) {
|
|
354
605
|
return `${variantPrefix}font-size`;
|
|
355
606
|
}
|
|
607
|
+
if (/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/u.test(utility)) {
|
|
608
|
+
return `${variantPrefix}font-weight`;
|
|
609
|
+
}
|
|
610
|
+
if (/^leading-/u.test(utility)) {
|
|
611
|
+
return `${variantPrefix}line-height`;
|
|
612
|
+
}
|
|
356
613
|
if (utility.startsWith("bg-")) {
|
|
357
614
|
return `${variantPrefix}background-color`;
|
|
358
615
|
}
|
|
616
|
+
if (utility.startsWith("rounded")) {
|
|
617
|
+
return `${variantPrefix}${roundedConflictKey(utility)}`;
|
|
618
|
+
}
|
|
619
|
+
const border = borderConflictKey(utility);
|
|
620
|
+
if (border) {
|
|
621
|
+
return `${variantPrefix}${border}`;
|
|
622
|
+
}
|
|
623
|
+
if (/^flex-(row|row-reverse|col|col-reverse)$/u.test(utility)) {
|
|
624
|
+
return `${variantPrefix}flex-direction`;
|
|
625
|
+
}
|
|
626
|
+
if (/^flex-(wrap|wrap-reverse|nowrap)$/u.test(utility)) {
|
|
627
|
+
return `${variantPrefix}flex-wrap`;
|
|
628
|
+
}
|
|
629
|
+
if (/^grid-cols-/u.test(utility)) {
|
|
630
|
+
return `${variantPrefix}grid-template-columns`;
|
|
631
|
+
}
|
|
632
|
+
if (/^grid-rows-/u.test(utility)) {
|
|
633
|
+
return `${variantPrefix}grid-template-rows`;
|
|
634
|
+
}
|
|
635
|
+
if (/^gap[xy]?-/u.test(utility)) {
|
|
636
|
+
return `${variantPrefix}${utility.startsWith("gap-x-") ? "gap-x" : utility.startsWith("gap-y-") ? "gap-y" : "gap"}`;
|
|
637
|
+
}
|
|
359
638
|
return void 0;
|
|
360
639
|
}
|
|
361
640
|
function spacingConflictKey(utility) {
|
|
@@ -396,6 +675,57 @@ function splitVariant(className) {
|
|
|
396
675
|
function stripImportant(utility) {
|
|
397
676
|
return utility.startsWith("!") ? utility.slice(1) : utility;
|
|
398
677
|
}
|
|
678
|
+
function compareVariants(left, right) {
|
|
679
|
+
const leftParts = left ? left.split(":") : [];
|
|
680
|
+
const rightParts = right ? right.split(":") : [];
|
|
681
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
682
|
+
for (let index = 0; index < length; index += 1) {
|
|
683
|
+
const leftPart = leftParts[index] ?? "";
|
|
684
|
+
const rightPart = rightParts[index] ?? "";
|
|
685
|
+
const delta = variantRank(leftPart) - variantRank(rightPart);
|
|
686
|
+
if (delta !== 0) {
|
|
687
|
+
return delta;
|
|
688
|
+
}
|
|
689
|
+
const lexical = leftPart.localeCompare(rightPart);
|
|
690
|
+
if (lexical !== 0) {
|
|
691
|
+
return lexical;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return 0;
|
|
695
|
+
}
|
|
696
|
+
function variantRank(variant) {
|
|
697
|
+
if (!variant) {
|
|
698
|
+
return -1;
|
|
699
|
+
}
|
|
700
|
+
const index = variantOrder.indexOf(variant);
|
|
701
|
+
return index >= 0 ? index : 1e3;
|
|
702
|
+
}
|
|
703
|
+
function roundedConflictKey(utility) {
|
|
704
|
+
const match = /^rounded(?:-(?<side>[trbl]|tl|tr|br|bl))?-/u.exec(utility);
|
|
705
|
+
return match?.groups?.side ? `border-radius-${match.groups.side}` : "border-radius";
|
|
706
|
+
}
|
|
707
|
+
function borderConflictKey(utility) {
|
|
708
|
+
if (utility === "border-collapse" || utility === "border-separate") {
|
|
709
|
+
return "border-collapse";
|
|
710
|
+
}
|
|
711
|
+
const match = /^border(?:-(?<side>[trblxy]))?(?:-(?<value>.+))?$/u.exec(utility);
|
|
712
|
+
if (!match?.groups) {
|
|
713
|
+
return void 0;
|
|
714
|
+
}
|
|
715
|
+
const side = match.groups.side;
|
|
716
|
+
const value = match.groups.value;
|
|
717
|
+
const sideSuffix = side ? `-${side}` : "";
|
|
718
|
+
if (!value || isBorderWidthValue(value)) {
|
|
719
|
+
return `border-width${sideSuffix}`;
|
|
720
|
+
}
|
|
721
|
+
if (!side && borderStyleClasses.has(value)) {
|
|
722
|
+
return "border-style";
|
|
723
|
+
}
|
|
724
|
+
return `border-color${sideSuffix}`;
|
|
725
|
+
}
|
|
726
|
+
function isBorderWidthValue(value) {
|
|
727
|
+
return /^(0|2|4|8|\d+|\[.+\])$/u.test(value);
|
|
728
|
+
}
|
|
399
729
|
function rankUtility(utility) {
|
|
400
730
|
if (utility.startsWith("container")) return 0;
|
|
401
731
|
if (positionClasses.has(utility) || utility.startsWith("inset-")) return 10;
|
|
@@ -436,11 +766,14 @@ var TailwindCleaner = class {
|
|
|
436
766
|
/** Returns source text with duplicated and unordered Tailwind classes normalized. */
|
|
437
767
|
static fix(context) {
|
|
438
768
|
const ast = parseSource(context.source);
|
|
439
|
-
const segments = collectClassSegments(ast);
|
|
769
|
+
const segments = collectClassSegments(ast, context.config.tailwindFunctions);
|
|
440
770
|
const magic = new MagicString2(context.source);
|
|
441
771
|
const conflicts = [];
|
|
442
772
|
for (const segment of segments) {
|
|
443
|
-
const nextValue = normalizeClassList(segment.value,
|
|
773
|
+
const nextValue = normalizeClassList(segment.value, {
|
|
774
|
+
removeDuplicates: context.config.removeDuplicateClasses,
|
|
775
|
+
sortClasses: context.config.sortTailwindClasses
|
|
776
|
+
});
|
|
444
777
|
if (nextValue !== segment.value) {
|
|
445
778
|
magic.overwrite(segment.start, segment.end, nextValue);
|
|
446
779
|
}
|
|
@@ -462,8 +795,9 @@ var TailwindCleaner = class {
|
|
|
462
795
|
};
|
|
463
796
|
}
|
|
464
797
|
};
|
|
465
|
-
function collectClassSegments(ast) {
|
|
798
|
+
function collectClassSegments(ast, tailwindFunctions) {
|
|
466
799
|
const segments = [];
|
|
800
|
+
const functionNames = new Set(tailwindFunctions);
|
|
467
801
|
walkNode(ast, (node) => {
|
|
468
802
|
if (t3.isJSXAttribute(node)) {
|
|
469
803
|
if (!isClassAttribute(node)) {
|
|
@@ -480,6 +814,8 @@ function collectClassSegments(ast) {
|
|
|
480
814
|
segments.push(segmentFromTemplateElement(quasi));
|
|
481
815
|
}
|
|
482
816
|
}
|
|
817
|
+
} else if (t3.isCallExpression(node) && isTailwindFunctionCall(node, functionNames)) {
|
|
818
|
+
collectCallExpressionSegments(node, segments);
|
|
483
819
|
}
|
|
484
820
|
});
|
|
485
821
|
return segments;
|
|
@@ -507,6 +843,74 @@ function segmentFromTemplateElement(node) {
|
|
|
507
843
|
line: node.loc?.start.line ?? 1
|
|
508
844
|
};
|
|
509
845
|
}
|
|
846
|
+
function collectCallExpressionSegments(node, segments) {
|
|
847
|
+
for (const argument of node.arguments) {
|
|
848
|
+
collectExpressionSegments(argument, segments);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function collectExpressionSegments(node, segments) {
|
|
852
|
+
if (t3.isStringLiteral(node)) {
|
|
853
|
+
segments.push(segmentFromStringLiteral(node));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (t3.isTemplateLiteral(node) && node.expressions.length === 0) {
|
|
857
|
+
const quasi = node.quasis[0];
|
|
858
|
+
if (quasi) {
|
|
859
|
+
segments.push(segmentFromTemplateElement(quasi));
|
|
860
|
+
}
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (t3.isArrayExpression(node)) {
|
|
864
|
+
for (const element of node.elements) {
|
|
865
|
+
if (element) {
|
|
866
|
+
collectExpressionSegments(element, segments);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (t3.isObjectExpression(node)) {
|
|
872
|
+
for (const property of node.properties) {
|
|
873
|
+
if (t3.isObjectProperty(property)) {
|
|
874
|
+
collectObjectPropertySegments(property, segments);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (t3.isConditionalExpression(node)) {
|
|
880
|
+
collectExpressionSegments(node.consequent, segments);
|
|
881
|
+
collectExpressionSegments(node.alternate, segments);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (t3.isLogicalExpression(node)) {
|
|
885
|
+
collectExpressionSegments(node.right, segments);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function collectObjectPropertySegments(node, segments) {
|
|
889
|
+
if (t3.isStringLiteral(node.key)) {
|
|
890
|
+
segments.push(segmentFromStringLiteral(node.key));
|
|
891
|
+
} else if (t3.isTemplateLiteral(node.key) && node.key.expressions.length === 0) {
|
|
892
|
+
const quasi = node.key.quasis[0];
|
|
893
|
+
if (quasi) {
|
|
894
|
+
segments.push(segmentFromTemplateElement(quasi));
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (t3.isExpression(node.value)) {
|
|
898
|
+
collectExpressionSegments(node.value, segments);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function isTailwindFunctionCall(node, functionNames) {
|
|
902
|
+
const name = calleeName(node.callee);
|
|
903
|
+
return name ? functionNames.has(name) : false;
|
|
904
|
+
}
|
|
905
|
+
function calleeName(node) {
|
|
906
|
+
if (t3.isIdentifier(node)) {
|
|
907
|
+
return node.name;
|
|
908
|
+
}
|
|
909
|
+
if (t3.isMemberExpression(node) && t3.isIdentifier(node.property) && !node.computed) {
|
|
910
|
+
return node.property.name;
|
|
911
|
+
}
|
|
912
|
+
return void 0;
|
|
913
|
+
}
|
|
510
914
|
function detectConflicts(file, line, classes) {
|
|
511
915
|
const groups = /* @__PURE__ */ new Map();
|
|
512
916
|
for (const className of classes) {
|
|
@@ -612,46 +1016,104 @@ async function processProjectFiles(options, mode) {
|
|
|
612
1016
|
const result = runCleaner(ImportCleaner, mode, { filePath, source: output, config });
|
|
613
1017
|
issues.push(...result.issues);
|
|
614
1018
|
output = result.output ?? output;
|
|
1019
|
+
if (hasParseIssue(result.issues)) {
|
|
1020
|
+
processed.push({ filePath, source, output, issues, conflicts });
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
615
1023
|
}
|
|
616
1024
|
if (config.tailwind) {
|
|
617
1025
|
const result = runCleaner(TailwindCleaner, mode, { filePath, source: output, config });
|
|
618
1026
|
issues.push(...result.issues);
|
|
619
1027
|
conflicts.push(...result.conflicts ?? []);
|
|
620
1028
|
output = result.output ?? output;
|
|
1029
|
+
if (hasParseIssue(result.issues)) {
|
|
1030
|
+
processed.push({ filePath, source, output, issues, conflicts });
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (shouldFormat(config, options)) {
|
|
1035
|
+
const result = await formatWithPrettier(filePath, output);
|
|
1036
|
+
issues.push(...result.issues);
|
|
1037
|
+
output = result.output;
|
|
621
1038
|
}
|
|
622
1039
|
processed.push({ filePath, source, output, issues, conflicts });
|
|
623
1040
|
}
|
|
624
1041
|
return processed;
|
|
625
1042
|
}
|
|
626
1043
|
function runCleaner(cleaner, mode, context) {
|
|
627
|
-
|
|
1044
|
+
try {
|
|
1045
|
+
return mode === "check" ? cleaner.check(context) : cleaner.fix(context);
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
return {
|
|
1048
|
+
output: context.source,
|
|
1049
|
+
issues: [
|
|
1050
|
+
{
|
|
1051
|
+
file: context.filePath,
|
|
1052
|
+
line: 1,
|
|
1053
|
+
kind: "parse",
|
|
1054
|
+
message: `Unable to parse file: ${errorMessage(error)}`
|
|
1055
|
+
}
|
|
1056
|
+
]
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function shouldFormat(config, options) {
|
|
1061
|
+
return options.format === true || config.format === "prettier";
|
|
1062
|
+
}
|
|
1063
|
+
async function formatWithPrettier(filePath, source) {
|
|
1064
|
+
try {
|
|
1065
|
+
const resolvedConfig = await prettier.resolveConfig(filePath);
|
|
1066
|
+
const output = await prettier.format(source, {
|
|
1067
|
+
...resolvedConfig,
|
|
1068
|
+
filepath: filePath
|
|
1069
|
+
});
|
|
1070
|
+
return { output, issues: [] };
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
return {
|
|
1073
|
+
output: source,
|
|
1074
|
+
issues: [
|
|
1075
|
+
{
|
|
1076
|
+
file: filePath,
|
|
1077
|
+
line: 1,
|
|
1078
|
+
kind: "format",
|
|
1079
|
+
message: `Unable to format with Prettier: ${errorMessage(error)}`
|
|
1080
|
+
}
|
|
1081
|
+
]
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function errorMessage(error) {
|
|
1086
|
+
return error instanceof Error ? error.message : String(error);
|
|
1087
|
+
}
|
|
1088
|
+
function hasParseIssue(issues) {
|
|
1089
|
+
return issues.some((issue) => issue.kind === "parse");
|
|
628
1090
|
}
|
|
629
1091
|
|
|
630
1092
|
// src/index.ts
|
|
1093
|
+
var defaultListLimit = 12;
|
|
631
1094
|
var program = new Command();
|
|
632
|
-
program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.1
|
|
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) => {
|
|
1095
|
+
program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.3.1");
|
|
1096
|
+
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
1097
|
const runOptions = toRunOptions(options);
|
|
635
1098
|
if (options.write) {
|
|
636
1099
|
const result2 = await fix({ ...runOptions, write: true });
|
|
637
|
-
printFixResult(result2, options.verbose ?? false);
|
|
1100
|
+
printFixResult(result2, options.verbose ?? false, runOptions.cwd ?? process.cwd());
|
|
638
1101
|
process.exitCode = result2.conflicts.length > 0 ? 1 : 0;
|
|
639
1102
|
return;
|
|
640
1103
|
}
|
|
641
1104
|
const result = await check(runOptions);
|
|
642
|
-
printCheckResult(result, options.verbose ?? false);
|
|
1105
|
+
printCheckResult(result, options.verbose ?? false, runOptions.cwd ?? process.cwd());
|
|
643
1106
|
process.exitCode = result.ok ? 0 : 1;
|
|
644
1107
|
});
|
|
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) => {
|
|
1108
|
+
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
1109
|
const result = await fix({
|
|
647
1110
|
...toRunOptions(options),
|
|
648
1111
|
write: options.check ? false : options.write ?? true,
|
|
649
1112
|
staged: options.staged ?? false
|
|
650
1113
|
});
|
|
651
|
-
printFixResult(result, options.verbose ?? false);
|
|
1114
|
+
printFixResult(result, options.verbose ?? false, options.cwd ?? process.cwd());
|
|
652
1115
|
process.exitCode = result.conflicts.length > 0 ? 1 : 0;
|
|
653
1116
|
});
|
|
654
|
-
await program.parseAsync();
|
|
655
1117
|
function toRunOptions(options) {
|
|
656
1118
|
const runOptions = {};
|
|
657
1119
|
if (options.cwd !== void 0) runOptions.cwd = options.cwd;
|
|
@@ -660,33 +1122,128 @@ function toRunOptions(options) {
|
|
|
660
1122
|
if (options.check !== void 0) runOptions.check = options.check;
|
|
661
1123
|
if (options.verbose !== void 0) runOptions.verbose = options.verbose;
|
|
662
1124
|
if (options.staged !== void 0) runOptions.staged = options.staged;
|
|
1125
|
+
if (options.format !== void 0) runOptions.format = options.format;
|
|
663
1126
|
return runOptions;
|
|
664
1127
|
}
|
|
665
|
-
function printCheckResult(result, verbose) {
|
|
1128
|
+
function printCheckResult(result, verbose, cwd) {
|
|
666
1129
|
if (result.ok) {
|
|
667
|
-
console.log("cleanwind
|
|
1130
|
+
console.log(`${color.green("cleanwind")} all files are clean.`);
|
|
668
1131
|
return;
|
|
669
1132
|
}
|
|
670
|
-
console.log(
|
|
671
|
-
|
|
1133
|
+
console.log(`${color.bold("cleanwind check")}`);
|
|
1134
|
+
console.log(
|
|
1135
|
+
summaryLine(
|
|
1136
|
+
result.changedFiles.length,
|
|
1137
|
+
result.conflicts.length,
|
|
1138
|
+
result.changedFiles.length === 1 ? "needs cleanup" : "need cleanup"
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
printFileList("Files", result.changedFiles, cwd, verbose);
|
|
1142
|
+
printDiagnostics(result, verbose, cwd);
|
|
1143
|
+
printNextStep(result);
|
|
672
1144
|
}
|
|
673
|
-
function printFixResult(result, verbose) {
|
|
1145
|
+
function printFixResult(result, verbose, cwd) {
|
|
674
1146
|
const written = result.writtenFiles.length;
|
|
675
1147
|
const changed = result.changedFiles.length;
|
|
676
1148
|
const mode = written > 0 ? "fixed" : "would change";
|
|
677
|
-
console.log(
|
|
678
|
-
|
|
1149
|
+
console.log(`${color.bold("cleanwind fix")}`);
|
|
1150
|
+
console.log(summaryLine(written > 0 ? written : changed, result.conflicts.length, mode));
|
|
1151
|
+
printFileList(written > 0 ? "Written" : "Would change", written > 0 ? result.writtenFiles : result.changedFiles, cwd, verbose);
|
|
1152
|
+
printDiagnostics(result, verbose, cwd);
|
|
1153
|
+
printNextStep(result);
|
|
1154
|
+
}
|
|
1155
|
+
function summaryLine(fileCount, conflictCount, mode) {
|
|
1156
|
+
const files = `${fileCount} file${fileCount === 1 ? "" : "s"}`;
|
|
1157
|
+
const filePhrase = mode === "would change" ? `${mode} ${files}` : `${files} ${mode}`;
|
|
1158
|
+
const conflicts = conflictCount > 0 ? `, ${color.yellow(`${conflictCount} conflict${conflictCount === 1 ? "" : "s"}`)}` : "";
|
|
1159
|
+
return `${color.cyan(filePhrase)}${conflicts}.`;
|
|
1160
|
+
}
|
|
1161
|
+
function printFileList(title, files, cwd, verbose) {
|
|
1162
|
+
if (files.length === 0) {
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const shown = verbose ? files : files.slice(0, defaultListLimit);
|
|
1166
|
+
console.log("");
|
|
1167
|
+
console.log(color.bold(title));
|
|
1168
|
+
for (const file of shown) {
|
|
1169
|
+
console.log(` ${formatPath(file, cwd)}`);
|
|
1170
|
+
}
|
|
1171
|
+
if (!verbose && files.length > shown.length) {
|
|
1172
|
+
console.log(color.dim(` ... ${files.length - shown.length} more. Run with --verbose to show all.`));
|
|
1173
|
+
}
|
|
679
1174
|
}
|
|
680
|
-
function printDiagnostics(result, verbose) {
|
|
681
|
-
|
|
1175
|
+
function printDiagnostics(result, verbose, cwd) {
|
|
1176
|
+
printConflicts(result, verbose, cwd);
|
|
1177
|
+
printIssues(result, verbose, cwd);
|
|
1178
|
+
}
|
|
1179
|
+
function printConflicts(result, verbose, cwd) {
|
|
1180
|
+
if (result.conflicts.length === 0) {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const conflicts = verbose ? result.conflicts : result.conflicts.slice(0, defaultListLimit);
|
|
1184
|
+
let currentFile = "";
|
|
1185
|
+
console.error("");
|
|
1186
|
+
console.error(color.bold("Conflicts"));
|
|
1187
|
+
for (const conflict of conflicts) {
|
|
1188
|
+
const file = formatPath(conflict.file, cwd);
|
|
1189
|
+
if (file !== currentFile) {
|
|
1190
|
+
currentFile = file;
|
|
1191
|
+
console.error(` ${color.cyan(file)}`);
|
|
1192
|
+
}
|
|
1193
|
+
console.error(
|
|
1194
|
+
` ${color.dim(String(conflict.line).padStart(4, " "))} ${conflict.conflictingClasses.join(" + ")}`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
if (!verbose && result.conflicts.length > conflicts.length) {
|
|
682
1198
|
console.error(
|
|
683
|
-
|
|
1199
|
+
color.dim(
|
|
1200
|
+
` ... ${result.conflicts.length - conflicts.length} more conflict(s). Run with --verbose to show all.`
|
|
1201
|
+
)
|
|
684
1202
|
);
|
|
685
1203
|
}
|
|
1204
|
+
}
|
|
1205
|
+
function printIssues(result, verbose, cwd) {
|
|
686
1206
|
if (!verbose) {
|
|
687
1207
|
return;
|
|
688
1208
|
}
|
|
689
|
-
|
|
690
|
-
|
|
1209
|
+
const issues = result.issues.filter((issue) => issue.kind !== "conflict");
|
|
1210
|
+
if (issues.length === 0) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
console.error("");
|
|
1214
|
+
console.error(color.bold("Diagnostics"));
|
|
1215
|
+
for (const issue of issues) {
|
|
1216
|
+
console.error(
|
|
1217
|
+
` ${color.cyan(formatPath(issue.file, cwd))}:${issue.line} ${color.dim(`[${issue.kind}]`)} ${issue.message}`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function printNextStep(result) {
|
|
1222
|
+
if (result.conflicts.length > 0) {
|
|
1223
|
+
console.log("");
|
|
1224
|
+
console.log(color.dim("Resolve conflicts manually, then run: npx cleanwind fix --format"));
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if (result.changedFiles.length > 0) {
|
|
1228
|
+
console.log("");
|
|
1229
|
+
console.log(color.dim("Apply cleanup with: npx cleanwind fix --format"));
|
|
691
1230
|
}
|
|
692
1231
|
}
|
|
1232
|
+
function formatPath(file, cwd) {
|
|
1233
|
+
const relative = path4.relative(path4.resolve(cwd), file);
|
|
1234
|
+
return (relative && !relative.startsWith("..") ? relative : file).replaceAll("\\", "/");
|
|
1235
|
+
}
|
|
1236
|
+
function supportsColor() {
|
|
1237
|
+
return process.env.NO_COLOR === void 0 && process.stdout.isTTY === true;
|
|
1238
|
+
}
|
|
1239
|
+
function paint(code, value) {
|
|
1240
|
+
return supportsColor() ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
1241
|
+
}
|
|
1242
|
+
var color = {
|
|
1243
|
+
bold: (value) => paint("1", value),
|
|
1244
|
+
cyan: (value) => paint("36", value),
|
|
1245
|
+
dim: (value) => paint("2", value),
|
|
1246
|
+
green: (value) => paint("32", value),
|
|
1247
|
+
yellow: (value) => paint("33", value)
|
|
1248
|
+
};
|
|
1249
|
+
await program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cleanwind",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
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:*"
|