tailwind-lint 0.14.0 → 0.15.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 CHANGED
@@ -75,6 +75,7 @@ tailwind-lint --verbose
75
75
  - `-c, --config <path>` - Path to Tailwind config file (default: auto-discover)
76
76
  - `-a, --auto` - Auto-discover files from the resolved Tailwind config or CSS `@source` directives
77
77
  - `--fix` - Automatically fix problems that can be fixed
78
+ - `--dry-run` - Show the fixes `--fix` would apply (with a line diff) without writing any files
78
79
  - `--format <text|json>` - Output format (`text` default, `json` for machine-readable output)
79
80
  - `-v, --verbose` - Enable verbose logging for debugging
80
81
  - `-h, --help` - Show help message
@@ -95,6 +96,9 @@ tailwind-lint --config ./src/app.css
95
96
  # Lint and fix specific file types
96
97
  tailwind-lint "**/*.vue" --fix
97
98
 
99
+ # Preview fixes without changing files
100
+ tailwind-lint --dry-run
101
+
98
102
  # Lint with a specific CSS config (v4)
99
103
  tailwind-lint --config ./styles/app.css
100
104
 
@@ -116,8 +120,8 @@ tailwind-lint --auto --format json
116
120
  The JSON payload includes:
117
121
 
118
122
  - `ok` - `true` when no errors are found
119
- - `summary` - counts for `errors`, `warnings`, `fixed`, `filesWithIssues`, `totalFilesProcessed`
120
- - `config` - resolved runtime values (`cwd`, `configPath`, `autoDiscover`, `fix`, `patterns`)
123
+ - `summary` - counts for `errors`, `warnings`, `fixable`, `fixed`, `filesWithIssues`, `totalFilesProcessed`
124
+ - `config` - resolved runtime values (`cwd`, `configPath`, `autoDiscover`, `fix`, `dryRun`, `patterns`)
121
125
  - `files[]` - per-file diagnostics with 1-based `line`/`column` ranges
122
126
 
123
127
  On JSON errors, the CLI keeps the same top-level shape and adds `error` so agent integrations do not need a separate parser for failure cases.
@@ -228,7 +232,9 @@ Use `--fix` to automatically resolve issues:
228
232
  - **Canonical class suggestions** (v4) - Replaces arbitrary values with standard classes
229
233
  - **Recommended variant order** (v3 & v4) - Reorders variants correctly
230
234
 
231
- Files are written atomically with multiple iterations to ensure all fixes are applied. The autofix process has a safety limit of 100 iterations per file to prevent infinite loops.
235
+ Use `--dry-run` to preview the changes without writing them.
236
+
237
+ Fixes that do not overlap are applied together, and files are revalidated until no fixable issues remain. The autofix process has a safety limit of 100 iterations per file to prevent infinite loops.
232
238
 
233
239
  ## Features
234
240
 
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_linter = require("./linter-B-Bi_uRJ.cjs");
2
+ const require_linter = require("./linter-CUpiAi7p.cjs");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_linter.__toESM(node_path, 1);
5
5
  let ansis = require("ansis");
@@ -7,7 +7,40 @@ ansis = require_linter.__toESM(ansis, 1);
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_linter.__toESM(node_fs, 1);
9
9
  let commander = require("commander");
10
- //#region src/output.ts
10
+ // src/diff.ts
11
+ /**
12
+ * Minimal line diff for previewing fixes. Quickfixes rewrite class lists in
13
+ * place, so lines usually map one-to-one; otherwise the region between the
14
+ * common prefix and suffix is reported as removed/added.
15
+ */
16
+ function diffLines(before, after) {
17
+ const a = before.split("\n");
18
+ const b = after.split("\n");
19
+ const changes = [];
20
+ if (a.length === b.length) {
21
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) changes.push({
22
+ line: i + 1,
23
+ removed: a[i],
24
+ added: b[i]
25
+ });
26
+ return changes;
27
+ }
28
+ let start = 0;
29
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
30
+ let endA = a.length;
31
+ let endB = b.length;
32
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
33
+ endA--;
34
+ endB--;
35
+ }
36
+ for (let i = start; i < Math.max(endA, endB); i++) changes.push({
37
+ line: i + 1,
38
+ removed: i < endA ? a[i] : void 0,
39
+ added: i < endB ? b[i] : void 0
40
+ });
41
+ return changes;
42
+ }
43
+ // src/output.ts
11
44
  function countBySeverity(diagnostics) {
12
45
  let errors = 0;
13
46
  let warnings = 0;
@@ -20,6 +53,15 @@ function countBySeverity(diagnostics) {
20
53
  warnings
21
54
  };
22
55
  }
56
+ const ALWAYS_FIXABLE_CODES = /* @__PURE__ */ new Set(["cssConflict", "invalidApply"]);
57
+ function isFixable(diagnostic) {
58
+ if (ALWAYS_FIXABLE_CODES.has(String(diagnostic.code))) return true;
59
+ const { suggestions } = diagnostic;
60
+ return Array.isArray(suggestions) && suggestions.length > 0;
61
+ }
62
+ function countFixable(diagnostics) {
63
+ return diagnostics.filter(isFixable).length;
64
+ }
23
65
  function toJsonSeverity(severity) {
24
66
  if (severity === require_linter.SEVERITY.ERROR) return "error";
25
67
  if (severity === require_linter.SEVERITY.WARNING) return "warning";
@@ -33,20 +75,22 @@ function toJsonDiagnostic(diagnostic) {
33
75
  endColumn: diagnostic.range.end.character + 1,
34
76
  severity: toJsonSeverity(diagnostic.severity),
35
77
  code: diagnostic.code ?? null,
36
- message: diagnostic.message,
78
+ message: typeof diagnostic.message === "string" ? diagnostic.message : diagnostic.message.value,
37
79
  source: diagnostic.source
38
80
  };
39
81
  }
40
- function createJsonOutput({ files, totalFilesProcessed, skippedFiles = [], cwd, configPath, autoDiscover, fix, patterns }) {
82
+ function createJsonOutput({ files, totalFilesProcessed, skippedFiles = [], cwd, configPath, autoDiscover, fix, dryRun = false, patterns }) {
41
83
  let errors = 0;
42
84
  let warnings = 0;
43
85
  let fixed = 0;
86
+ let fixable = 0;
44
87
  let filesWithIssues = 0;
45
88
  const mappedFiles = files.map((file) => {
46
89
  const severityCount = countBySeverity(file.diagnostics);
47
90
  errors += severityCount.errors;
48
91
  warnings += severityCount.warnings;
49
92
  fixed += file.fixedCount || 0;
93
+ fixable += countFixable(file.diagnostics);
50
94
  if (file.diagnostics.length > 0) filesWithIssues++;
51
95
  return {
52
96
  path: file.path,
@@ -61,6 +105,7 @@ function createJsonOutput({ files, totalFilesProcessed, skippedFiles = [], cwd,
61
105
  errors,
62
106
  warnings,
63
107
  fixed,
108
+ fixable,
64
109
  filesWithIssues,
65
110
  totalFilesProcessed
66
111
  },
@@ -69,13 +114,14 @@ function createJsonOutput({ files, totalFilesProcessed, skippedFiles = [], cwd,
69
114
  configPath: configPath || null,
70
115
  autoDiscover,
71
116
  fix,
117
+ dryRun,
72
118
  patterns
73
119
  },
74
120
  files: mappedFiles,
75
121
  skippedFiles
76
122
  };
77
123
  }
78
- function createJsonErrorOutput({ error, cwd, configPath, autoDiscover, fix, patterns }) {
124
+ function createJsonErrorOutput({ error, cwd, configPath, autoDiscover, fix, dryRun = false, patterns }) {
79
125
  return {
80
126
  ok: false,
81
127
  error,
@@ -83,6 +129,7 @@ function createJsonErrorOutput({ error, cwd, configPath, autoDiscover, fix, patt
83
129
  errors: 0,
84
130
  warnings: 0,
85
131
  fixed: 0,
132
+ fixable: 0,
86
133
  filesWithIssues: 0,
87
134
  totalFilesProcessed: 0
88
135
  },
@@ -91,15 +138,17 @@ function createJsonErrorOutput({ error, cwd, configPath, autoDiscover, fix, patt
91
138
  configPath: configPath || null,
92
139
  autoDiscover,
93
140
  fix,
141
+ dryRun,
94
142
  patterns
95
143
  },
96
144
  files: [],
97
145
  skippedFiles: []
98
146
  };
99
147
  }
100
- //#endregion
101
- //#region src/cli.ts
148
+ // src/cli.ts
102
149
  const MAX_FILENAME_DISPLAY_LENGTH = 50;
150
+ const MAX_DIFF_LINES_PER_FILE = 10;
151
+ const CLEAR_LINE = "\r\x1B[2K";
103
152
  function resolveOptions(files, options) {
104
153
  const hasAutoFlag = !!options.auto;
105
154
  const hasFiles = files.length > 0;
@@ -113,66 +162,97 @@ function resolveOptions(files, options) {
113
162
  patterns = [];
114
163
  autoDiscover = true;
115
164
  }
165
+ const dryRun = options.dryRun || false;
116
166
  return {
117
167
  cwd,
118
168
  configPath,
119
169
  patterns: autoDiscover ? [] : patterns,
120
170
  autoDiscover,
121
- fix: options.fix || false,
171
+ fix: options.fix || dryRun,
172
+ dryRun,
122
173
  verbose: options.verbose || false
123
174
  };
124
175
  }
125
176
  function truncateFilename(filename) {
126
177
  return filename.length > MAX_FILENAME_DISPLAY_LENGTH ? `...${filename.slice(-50)}` : filename;
127
178
  }
128
- async function displayResults(files, fixMode) {
129
- let totalErrors = 0;
130
- let totalWarnings = 0;
131
- let totalFixed = 0;
132
- let filesWithIssues = 0;
133
- let isFirstFile = true;
134
- for (const file of files) if (file.diagnostics.length > 0 || fixMode && file.fixed) {
135
- if (isFirstFile) {
136
- console.log();
137
- isFirstFile = false;
138
- } else console.log();
139
- console.log(ansis.default.underline.bold(file.path));
140
- if (fixMode && file.fixed) {
141
- const issueText = `${file.fixedCount || 0} issue${file.fixedCount !== 1 ? "s" : ""}`;
142
- console.log(ansis.default.green(` ✔ Fixed ${issueText}`));
143
- totalFixed += file.fixedCount || 0;
179
+ function plural(count, word) {
180
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
181
+ }
182
+ function formatDuration(ms) {
183
+ return ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(2)}s`;
184
+ }
185
+ function formatDiff(file) {
186
+ if (file.originalContent === void 0 || file.fixedContent === void 0) return [];
187
+ const changes = diffLines(file.originalContent, file.fixedContent);
188
+ const shown = changes.slice(0, MAX_DIFF_LINES_PER_FILE);
189
+ const width = String(shown.at(-1)?.line ?? 0).length;
190
+ const lines = [];
191
+ for (const change of shown) {
192
+ const lineNo = ansis.default.dim(String(change.line).padStart(width));
193
+ const blank = " ".repeat(width);
194
+ if (change.removed !== void 0) lines.push(` ${lineNo} ${ansis.default.red(`- ${change.removed.trim()}`)}`);
195
+ if (change.added !== void 0) {
196
+ const prefix = change.removed !== void 0 ? blank : lineNo;
197
+ lines.push(` ${prefix} ${ansis.default.green(`+ ${change.added.trim()}`)}`);
144
198
  }
145
- const { errors, warnings } = countBySeverity(file.diagnostics);
146
- totalErrors += errors;
147
- totalWarnings += warnings;
199
+ }
200
+ const hidden = changes.length - shown.length;
201
+ if (hidden > 0) lines.push(ansis.default.dim(` … ${plural(hidden, "more changed line")}`));
202
+ return lines;
203
+ }
204
+ function formatFile(file, dryRun) {
205
+ const lines = [ansis.default.underline.bold(file.path)];
206
+ if (file.fixed) {
207
+ const issues = plural(file.fixedCount || 0, "issue");
208
+ lines.push(dryRun ? ansis.default.cyan(` ~ Would fix ${issues}`) : ansis.default.green(` ✔ Fixed ${issues}`));
209
+ if (dryRun) lines.push(...formatDiff(file));
210
+ }
211
+ const positions = file.diagnostics.map((d) => `${d.range.start.line + 1}:${d.range.start.character + 1}`);
212
+ const positionWidth = Math.max(0, ...positions.map((p) => p.length));
213
+ for (const [index, diagnostic] of file.diagnostics.entries()) {
214
+ const severity = diagnostic.severity === require_linter.SEVERITY.ERROR ? ansis.default.red("error ") : ansis.default.yellow("warning");
215
+ const code = diagnostic.code ? ` ${ansis.default.dim(String(diagnostic.code))}` : "";
216
+ lines.push(` ${ansis.default.dim(positions[index].padEnd(positionWidth))} ${severity} ${diagnostic.message}${code}`);
217
+ }
218
+ return lines;
219
+ }
220
+ function formatSummary(files, totalFilesProcessed, durationMs, fixMode, dryRun) {
221
+ let errors = 0;
222
+ let warnings = 0;
223
+ let fixed = 0;
224
+ let fixable = 0;
225
+ let filesWithIssues = 0;
226
+ let filesFixed = 0;
227
+ for (const file of files) {
228
+ const counts = countBySeverity(file.diagnostics);
229
+ errors += counts.errors;
230
+ warnings += counts.warnings;
231
+ fixable += countFixable(file.diagnostics);
148
232
  if (file.diagnostics.length > 0) filesWithIssues++;
149
- for (const diagnostic of file.diagnostics) {
150
- const line = diagnostic.range.start.line + 1;
151
- const char = diagnostic.range.start.character + 1;
152
- const severity = diagnostic.severity === require_linter.SEVERITY.ERROR ? "error" : "warning";
153
- const severityColor = diagnostic.severity === require_linter.SEVERITY.ERROR ? ansis.default.red(severity) : ansis.default.yellow(severity);
154
- const code = diagnostic.code ? ansis.default.dim(` (${diagnostic.code})`) : "";
155
- console.log(` ${ansis.default.dim(`${line}:${char}`)} ${severityColor} ${diagnostic.message}${code}`);
233
+ if (file.fixed) {
234
+ fixed += file.fixedCount || 0;
235
+ filesFixed++;
156
236
  }
157
237
  }
158
- console.log();
159
- if (totalErrors === 0 && totalWarnings === 0) {
160
- if (totalFixed > 0) {
161
- const issueText = `${totalFixed} issue${totalFixed !== 1 ? "s" : ""}`;
162
- console.log(ansis.default.green.bold(`✔ Fixed ${issueText}`));
163
- } else console.log(ansis.default.green.bold("✔ No issues found"));
238
+ const lines = [];
239
+ if (fixed > 0) {
240
+ const text = `${plural(fixed, "issue")} in ${plural(filesFixed, "file")}`;
241
+ lines.push(dryRun ? ansis.default.cyan.bold(`~ Would fix ${text} (dry run, no files written)`) : ansis.default.green.bold(`✔ Fixed ${text}`));
242
+ }
243
+ const problems = errors + warnings;
244
+ if (problems === 0) {
245
+ if (fixed === 0) lines.push(ansis.default.green.bold("✔ No issues found"));
164
246
  } else {
165
- const parts = [];
166
- if (totalErrors > 0) parts.push(`${totalErrors} error${totalErrors !== 1 ? "s" : ""}`);
167
- if (totalWarnings > 0) parts.push(`${totalWarnings} warning${totalWarnings !== 1 ? "s" : ""}`);
168
- const fileText = `${filesWithIssues} file${filesWithIssues !== 1 ? "s" : ""}`;
169
- const summary = `Found ${parts.join(" and ")} in ${fileText}`;
170
- if (totalFixed > 0) {
171
- const issueText = `${totalFixed} issue${totalFixed !== 1 ? "s" : ""}`;
172
- console.log(ansis.default.green.bold(`✔ Fixed ${issueText}`));
173
- console.log(summary);
174
- } else console.log(summary);
247
+ const color = errors > 0 ? ansis.default.red.bold : ansis.default.yellow.bold;
248
+ lines.push(color(`✖ ${plural(problems, "problem")} (${plural(errors, "error")}, ${plural(warnings, "warning")}) in ${plural(filesWithIssues, "file")}`));
249
+ if (fixable > 0 && !fixMode) lines.push(ansis.default.dim(` ${fixable} potentially fixable with ${ansis.default.yellow("--fix")} (preview with ${ansis.default.yellow("--dry-run")})`));
175
250
  }
251
+ lines.push(ansis.default.dim(`Linted ${plural(totalFilesProcessed, "file")} in ${formatDuration(durationMs)}`));
252
+ return lines;
253
+ }
254
+ function writeAndExit(output, code) {
255
+ process.stdout.write(output, () => process.exit(code));
176
256
  }
177
257
  const program = new commander.Command();
178
258
  const getVersion = () => {
@@ -209,13 +289,14 @@ program.configureHelp({ formatHelp: (cmd, helper) => {
209
289
  }
210
290
  return output;
211
291
  } });
212
- program.name("tailwind-lint").description("A CLI tool for linting Tailwind CSS class usage").version(getVersion()).argument("[files...]", "File patterns to lint (e.g., \"src/**/*.{js,jsx,ts,tsx}\")").option("-c, --config <path>", "Path to Tailwind config file (default: auto-discover)").option("-a, --auto", "Auto-discover files from Tailwind config or CSS @source patterns").option("--fix", "Automatically fix problems that can be fixed").option("-v, --verbose", "Enable verbose logging for debugging").option("--format <format>", "Output format: text or json", "text").addHelpText("after", `
292
+ program.name("tailwind-lint").description("A CLI tool for linting Tailwind CSS class usage").version(getVersion()).argument("[files...]", "File patterns to lint (e.g., \"src/**/*.{js,jsx,ts,tsx}\")").option("-c, --config <path>", "Path to Tailwind config file (default: auto-discover)").option("-a, --auto", "Auto-discover files from Tailwind config or CSS @source patterns").option("--fix", "Automatically fix problems that can be fixed").option("--dry-run", "Show which fixes --fix would apply without writing any files").option("-v, --verbose", "Enable verbose logging for debugging").option("--format <format>", "Output format: text or json", "text").addHelpText("after", `
213
293
  ${ansis.default.bold.cyan("Examples:")}
214
294
  ${ansis.default.dim("$")} tailwind-lint
215
295
  ${ansis.default.dim("$")} tailwind-lint ${ansis.default.green("\"src/**/*.{js,jsx,ts,tsx}\"")}
216
296
  ${ansis.default.dim("$")} tailwind-lint ${ansis.default.yellow("--config")} ${ansis.default.green("./tailwind.config.js")}
217
297
  ${ansis.default.dim("$")} tailwind-lint ${ansis.default.yellow("--config")} ${ansis.default.green("./src/app.css")}
218
298
  ${ansis.default.dim("$")} tailwind-lint ${ansis.default.green("\"src/**/*.tsx\"")} ${ansis.default.yellow("--fix")}
299
+ ${ansis.default.dim("$")} tailwind-lint ${ansis.default.yellow("--dry-run")}
219
300
  ${ansis.default.dim("$")} tailwind-lint ${ansis.default.green("\"**/*.vue\"")}
220
301
 
221
302
  ${ansis.default.bold.cyan("Notes:")}
@@ -226,73 +307,60 @@ ${ansis.default.bold.cyan("Notes:")}
226
307
  ${ansis.default.dim("•")} Default pattern: ${ansis.default.dim(require_linter.DEFAULT_FILE_PATTERN)}
227
308
  ${ansis.default.dim("•")} Reads Tailwind workspace settings from ${ansis.default.dim(".zed/settings.json")} and ${ansis.default.dim(".vscode/settings.json")}
228
309
  ${ansis.default.dim("•")} Use ${ansis.default.yellow("--fix")} to automatically resolve fixable issues
310
+ ${ansis.default.dim("•")} Use ${ansis.default.yellow("--dry-run")} to preview fixes without changing files
229
311
  `).action(async (files, options) => {
230
312
  const hasConfigFlag = !!options.config;
231
313
  const hasAutoFlag = !!options.auto;
232
314
  if (!(files.length > 0) && !hasAutoFlag && !hasConfigFlag) options.auto = true;
233
315
  const resolved = resolveOptions(files, options);
234
- const isJsonOutput = (options.format === "json" ? "json" : "text") === "json";
316
+ const isJsonOutput = options.format === "json";
317
+ const showProgress = !isJsonOutput && !resolved.verbose && !!process.stdout.isTTY;
318
+ const startTime = performance.now();
235
319
  try {
236
320
  if (resolved.verbose && !isJsonOutput) {
237
321
  console.log(ansis.default.cyan.bold("→ Configuration"));
238
322
  console.log(ansis.default.dim(` Working directory: ${resolved.cwd}`));
239
323
  console.log(ansis.default.dim(` Config path: ${resolved.configPath || "auto-discover"}`));
240
324
  console.log(ansis.default.dim(` Fix mode: ${resolved.fix}`));
325
+ console.log(ansis.default.dim(` Dry run: ${resolved.dryRun}`));
241
326
  console.log(ansis.default.dim(` Patterns: ${resolved.patterns.length > 0 ? resolved.patterns.join(", ") : "auto-discover"}`));
242
327
  console.log();
243
328
  }
329
+ if (showProgress) process.stdout.write(`${ansis.default.cyan("→")} Loading Tailwind configuration...`);
244
330
  const results = await require_linter.lint({
245
331
  ...resolved,
246
332
  onProgress: (current, total, file) => {
247
- if (isJsonOutput) return;
248
- if (process.stdout.isTTY && !resolved.verbose) {
249
- const displayFile = truncateFilename(file);
250
- process.stdout.write(`\r${ansis.default.cyan("→")} Linting files... ${ansis.default.dim(`(${current}/${total})`)} ${ansis.default.dim(displayFile)}${" ".repeat(10)}`);
251
- } else if (resolved.verbose) console.log(ansis.default.dim(` [${current}/${total}] Linting ${file}`));
333
+ if (showProgress) process.stdout.write(`${CLEAR_LINE}${ansis.default.cyan("→")} Linting files... ${ansis.default.dim(`(${current}/${total})`)} ${ansis.default.dim(truncateFilename(file))}`);
334
+ else if (resolved.verbose && !isJsonOutput) console.log(ansis.default.dim(` [${current}/${total}] Linting ${file}`));
252
335
  }
253
336
  });
254
- if (process.stdout.isTTY && !resolved.verbose && !isJsonOutput) process.stdout.write(`\r${" ".repeat(80)}\r`);
255
- if (results.skippedFiles.length > 0 && !isJsonOutput) for (const skipped of results.skippedFiles) console.log(ansis.default.yellow(`⚠ Skipped ${truncateFilename(skipped)} (exceeds size limit)`));
256
- if (results.totalFilesProcessed === 0) {
257
- if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
258
- ...resolved,
259
- files: [],
260
- totalFilesProcessed: 0,
261
- skippedFiles: results.skippedFiles
262
- })));
263
- else {
264
- console.log();
265
- console.log(ansis.default.yellow("⚠ No files found to lint"));
266
- }
267
- process.exit(0);
268
- }
269
- if (results.files.length === 0) {
270
- if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
271
- ...resolved,
272
- files: [],
273
- totalFilesProcessed: results.totalFilesProcessed,
274
- skippedFiles: results.skippedFiles
275
- })));
276
- else console.log(ansis.default.green.bold("✔ No issues found"));
277
- process.exit(0);
278
- }
279
- if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
337
+ if (showProgress) process.stdout.write(CLEAR_LINE);
338
+ const exitCode = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_linter.SEVERITY.ERROR)) ? 1 : 0;
339
+ if (isJsonOutput) return writeAndExit(`${JSON.stringify(createJsonOutput({
280
340
  ...resolved,
281
341
  files: results.files,
282
342
  totalFilesProcessed: results.totalFilesProcessed,
283
343
  skippedFiles: results.skippedFiles
284
- })));
285
- else await displayResults(results.files, resolved.fix);
286
- const hasErrors = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_linter.SEVERITY.ERROR));
287
- process.exit(hasErrors ? 1 : 0);
344
+ }))}\n`, exitCode);
345
+ const lines = [];
346
+ for (const skipped of results.skippedFiles) lines.push(ansis.default.yellow(`⚠ Skipped ${truncateFilename(skipped)} (exceeds size limit)`));
347
+ if (results.totalFilesProcessed === 0) {
348
+ lines.push(ansis.default.yellow("⚠ No files found to lint"));
349
+ return writeAndExit(`${lines.join("\n")}\n`, 0);
350
+ }
351
+ for (const file of results.files) lines.push("", ...formatFile(file, resolved.dryRun));
352
+ if (results.files.length > 0) lines.push("");
353
+ lines.push(...formatSummary(results.files, results.totalFilesProcessed, performance.now() - startTime, resolved.fix, resolved.dryRun));
354
+ return writeAndExit(`${lines.join("\n")}\n`, exitCode);
288
355
  } catch (error) {
356
+ if (showProgress) process.stdout.write(CLEAR_LINE);
289
357
  const errorMessage = error instanceof Error ? error.message : String(error);
290
- if (isJsonOutput) console.log(JSON.stringify(createJsonErrorOutput({
358
+ if (isJsonOutput) return writeAndExit(`${JSON.stringify(createJsonErrorOutput({
291
359
  error: errorMessage,
292
360
  ...resolved
293
- })));
294
- else console.error(ansis.default.red("✖ Error:"), errorMessage);
295
- if (resolved.verbose && error instanceof Error && !isJsonOutput) {
361
+ }))}\n`, 1);
362
+ console.error(ansis.default.red("✖ Error:"), errorMessage);
363
+ if (resolved.verbose && error instanceof Error) {
296
364
  console.error(ansis.default.dim("\nStack trace:"));
297
365
  console.error(ansis.default.dim(error.stack || error.toString()));
298
366
  }
@@ -300,4 +368,3 @@ ${ansis.default.bold.cyan("Notes:")}
300
368
  }
301
369
  });
302
370
  program.parse();
303
- //#endregion
@@ -1,4 +1,4 @@
1
- //#region \0rolldown/runtime.js
1
+ // \0rolldown/runtime.js
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -19,7 +19,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
19
19
  value: mod,
20
20
  enumerable: true
21
21
  }) : target, mod));
22
- //#endregion
22
+ let node_fs_promises = require("node:fs/promises");
23
+ node_fs_promises = __toESM(node_fs_promises, 1);
23
24
  let node_path = require("node:path");
24
25
  node_path = __toESM(node_path, 1);
25
26
  let _tailwindcss_language_service = require("@tailwindcss/language-service");
@@ -33,8 +34,7 @@ node_fs = __toESM(node_fs, 1);
33
34
  let node_url = require("node:url");
34
35
  let postcss = require("postcss");
35
36
  postcss = __toESM(postcss, 1);
36
- let node_fs_promises = require("node:fs/promises");
37
- //#region src/constants.ts
37
+ // src/constants.ts
38
38
  const DEFAULT_IGNORE_PATTERNS = [
39
39
  "**/node_modules/**",
40
40
  "**/dist/**",
@@ -125,8 +125,7 @@ function getLanguageId(filePath) {
125
125
  const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
126
126
  return LANGUAGE_MAP[ext] || "html";
127
127
  }
128
- //#endregion
129
- //#region src/code-actions.ts
128
+ // src/code-actions.ts
130
129
  async function getQuickfixes(state, document, uri, diagnostics) {
131
130
  const lspDiagnostics = diagnostics.map((diag) => ({
132
131
  range: diag.range,
@@ -151,49 +150,85 @@ async function getQuickfixes(state, document, uri, diagnostics) {
151
150
  };
152
151
  return (await (0, _tailwindcss_language_service.doCodeActions)(state, params, document)).filter((action) => action.kind === "quickfix" || action.kind?.startsWith(`quickfix.`));
153
152
  }
154
- function applyFirstQuickfix(action, uri, document, content) {
155
- if (!action.edit?.changes?.[uri]) return null;
156
- const sortedEdits = [...action.edit.changes[uri]].sort((a, b) => {
157
- const lineDiff = b.range.start.line - a.range.start.line;
158
- if (lineDiff !== 0) return lineDiff;
159
- return b.range.start.character - a.range.start.character;
160
- });
161
- let newContent = content;
162
- for (const edit of sortedEdits) {
163
- const startOffset = document.offsetAt(edit.range.start);
164
- const endOffset = document.offsetAt(edit.range.end);
165
- newContent = newContent.substring(0, startOffset) + edit.newText + newContent.substring(endOffset);
153
+ function diagnosticKey(diagnostic) {
154
+ const { start, end } = diagnostic.range;
155
+ return `${diagnostic.code}:${start.line}:${start.character}:${end.line}:${end.character}:${diagnostic.message}`;
156
+ }
157
+ function overlaps(a, b) {
158
+ return a.start === b.start || a.start < b.end && b.start < a.end;
159
+ }
160
+ /**
161
+ * Picks the first quickfix for every diagnostic and keeps only those whose
162
+ * edits do not overlap, so a single validation pass can fix many issues.
163
+ * Overlapping fixes (e.g. two cssConflict fixes rewriting the same class list)
164
+ * are left for the next pass, which sees the updated document.
165
+ */
166
+ function selectNonOverlappingEdits(actions, uri, document) {
167
+ const handled = /* @__PURE__ */ new Set();
168
+ const accepted = [];
169
+ let fixCount = 0;
170
+ for (const action of actions) {
171
+ const edits = action.edit?.changes?.[uri];
172
+ if (!edits || edits.length === 0) continue;
173
+ const keys = (action.diagnostics ?? []).map(diagnosticKey);
174
+ if (keys.some((key) => handled.has(key))) continue;
175
+ const offsetEdits = edits.map((edit) => ({
176
+ start: document.offsetAt(edit.range.start),
177
+ end: document.offsetAt(edit.range.end),
178
+ newText: edit.newText
179
+ }));
180
+ if (offsetEdits.some((edit, index) => [...accepted, ...offsetEdits.slice(0, index)].some((other) => overlaps(edit, other)))) continue;
181
+ for (const key of keys) handled.add(key);
182
+ accepted.push(...offsetEdits);
183
+ fixCount++;
166
184
  }
167
- return { content: newContent };
185
+ return {
186
+ edits: accepted,
187
+ fixCount
188
+ };
189
+ }
190
+ function applyEdits(content, edits) {
191
+ const sorted = [...edits].sort((a, b) => a.start - b.start);
192
+ let result = "";
193
+ let cursor = 0;
194
+ for (const edit of sorted) {
195
+ result += content.slice(cursor, edit.start) + edit.newText;
196
+ cursor = edit.end;
197
+ }
198
+ return result + content.slice(cursor);
168
199
  }
169
200
  async function applyCodeActions(state, filePath, content, diagnostics) {
170
201
  if (diagnostics.length === 0) return {
171
202
  content,
172
203
  changed: false,
173
- fixedCount: 0
204
+ fixedCount: 0,
205
+ diagnostics
174
206
  };
175
207
  try {
176
208
  const languageId = getLanguageId(filePath);
177
209
  const uri = `file://${filePath}`;
178
210
  let currentDocument = vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content);
179
211
  let currentContent = content;
212
+ let currentDiagnostics = diagnostics;
180
213
  let totalFixed = 0;
181
- for (let iteration = 0; iteration < 100; iteration++) {
182
- const currentDiagnostics = await (0, _tailwindcss_language_service.doValidate)(state, currentDocument);
214
+ for (let iteration = 0;; iteration++) {
215
+ if (iteration > 0) currentDiagnostics = await (0, _tailwindcss_language_service.doValidate)(state, currentDocument);
183
216
  if (currentDiagnostics.length === 0) break;
184
- const quickfixes = await getQuickfixes(state, currentDocument, uri, currentDiagnostics);
185
- if (quickfixes.length === 0) break;
186
- const fixResult = applyFirstQuickfix(quickfixes[0], uri, currentDocument, currentContent);
187
- if (!fixResult) break;
188
- currentContent = fixResult.content;
217
+ if (iteration === 100) {
218
+ console.warn(`Warning: Reached maximum fix iterations (100) for ${filePath}. Some issues may remain unfixed.`);
219
+ break;
220
+ }
221
+ const { edits, fixCount } = selectNonOverlappingEdits(await getQuickfixes(state, currentDocument, uri, currentDiagnostics), uri, currentDocument);
222
+ if (fixCount === 0) break;
223
+ currentContent = applyEdits(currentContent, edits);
189
224
  currentDocument = vscode_languageserver_textdocument.TextDocument.create(uri, languageId, currentDocument.version + 1, currentContent);
190
- totalFixed++;
191
- if (iteration === 99) console.warn(`Warning: Reached maximum fix iterations (100) for ${filePath}. Some issues may remain unfixed.`);
225
+ totalFixed += fixCount;
192
226
  }
193
227
  return {
194
228
  content: currentContent,
195
229
  changed: currentContent !== content,
196
- fixedCount: totalFixed
230
+ fixedCount: totalFixed,
231
+ diagnostics: currentDiagnostics
197
232
  };
198
233
  } catch (error) {
199
234
  const message = error instanceof Error ? error.message : String(error);
@@ -202,14 +237,14 @@ async function applyCodeActions(state, filePath, content, diagnostics) {
202
237
  return {
203
238
  content,
204
239
  changed: false,
205
- fixedCount: 0
240
+ fixedCount: 0,
241
+ diagnostics
206
242
  };
207
243
  }
208
244
  throw error;
209
245
  }
210
246
  }
211
- //#endregion
212
- //#region src/utils/fs.ts
247
+ // src/utils/fs.ts
213
248
  function fileExists(filePath) {
214
249
  try {
215
250
  return node_fs.existsSync(filePath);
@@ -228,11 +263,6 @@ function readFileSync(filePath) {
228
263
  if (!filePath || typeof filePath !== "string") throw new TypeError("File path must be a non-empty string");
229
264
  return node_fs.readFileSync(filePath, "utf-8");
230
265
  }
231
- function writeFileSync(filePath, content) {
232
- if (!filePath || typeof filePath !== "string") throw new TypeError("File path must be a non-empty string");
233
- if (typeof content !== "string") throw new TypeError("Content must be a string");
234
- node_fs.writeFileSync(filePath, content, "utf-8");
235
- }
236
266
  function readGitignorePatterns(cwd) {
237
267
  const gitignorePath = node_path.join(cwd, ".gitignore");
238
268
  if (!fileExists(gitignorePath)) return [];
@@ -246,8 +276,7 @@ function readGitignorePatterns(cwd) {
246
276
  return [];
247
277
  }
248
278
  }
249
- //#endregion
250
- //#region src/utils/settings.ts
279
+ // src/utils/settings.ts
251
280
  function loadWorkspaceTailwindSettings(cwd) {
252
281
  return mergeTailwindSettings(readVsCodeTailwindSettings(cwd), readZedTailwindSettings(cwd));
253
282
  }
@@ -396,8 +425,7 @@ function mergeEditorSettings(defaultSettings, overrides) {
396
425
  }
397
426
  };
398
427
  }
399
- //#endregion
400
- //#region src/adapters/editor-state-adapter.ts
428
+ // src/adapters/editor-state-adapter.ts
401
429
  function isDirectory(filePath) {
402
430
  try {
403
431
  return node_fs.statSync(filePath).isDirectory();
@@ -480,8 +508,7 @@ function createEditorState(cwd) {
480
508
  }
481
509
  };
482
510
  }
483
- //#endregion
484
- //#region src/types.ts
511
+ // src/types.ts
485
512
  /**
486
513
  * Error thrown when adapter fails to load required modules
487
514
  */
@@ -493,8 +520,7 @@ var AdapterLoadError = class extends Error {
493
520
  this.name = "AdapterLoadError";
494
521
  }
495
522
  };
496
- //#endregion
497
- //#region src/adapters/v3-adapter.ts
523
+ // src/adapters/v3-adapter.ts
498
524
  const require$4 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
499
525
  function interopDefault(module) {
500
526
  return module && typeof module === "object" && "default" in module ? module.default : module;
@@ -543,8 +569,7 @@ function extractConfigMetadata(state) {
543
569
  state.blocklist = config.blocklist ?? [];
544
570
  if (config.corePlugins) state.corePlugins = Array.isArray(config.corePlugins) ? config.corePlugins : Object.keys(config.corePlugins);
545
571
  }
546
- //#endregion
547
- //#region src/adapters/v4-adapter.ts
572
+ // src/adapters/v4-adapter.ts
548
573
  const require$3 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
549
574
  function resolveModule(id, paths) {
550
575
  let firstError;
@@ -592,6 +617,21 @@ function fromPostcss(nodes) {
592
617
  }
593
618
  });
594
619
  }
620
+ function memoizeCanonicalize(designSystem) {
621
+ const canonicalize = designSystem.canonicalizeCandidates;
622
+ if (typeof canonicalize === "function") {
623
+ const cache = /* @__PURE__ */ new Map();
624
+ designSystem.canonicalizeCandidates = ((candidates, options) => {
625
+ const key = `${JSON.stringify(options ?? null)}\0${candidates.join(" ")}`;
626
+ let result = cache.get(key);
627
+ if (!result) {
628
+ result = canonicalize.call(designSystem, candidates, options);
629
+ cache.set(key, result);
630
+ }
631
+ return [...result];
632
+ });
633
+ }
634
+ }
595
635
  async function loadV4DesignSystem(state, resolvePaths, target, verbose = false) {
596
636
  try {
597
637
  const tailwindPath = require$3.resolve("tailwindcss", { paths: resolvePaths });
@@ -637,19 +677,29 @@ async function loadV4DesignSystem(state, resolvePaths, target, verbose = false)
637
677
  };
638
678
  }
639
679
  });
680
+ const compileUncached = (classes) => {
681
+ if (designSystem.candidatesToAst) return designSystem.candidatesToAst(classes);
682
+ if (designSystem.candidatesToCss) return designSystem.candidatesToCss(classes).map((css) => {
683
+ if (!css) return [];
684
+ try {
685
+ return fromPostcss(postcss.default.parse(css).nodes);
686
+ } catch {
687
+ return [];
688
+ }
689
+ });
690
+ return classes.map(() => []);
691
+ };
692
+ memoizeCanonicalize(designSystem);
693
+ const compileCache = /* @__PURE__ */ new Map();
640
694
  Object.assign(designSystem, {
641
695
  dependencies: () => /* @__PURE__ */ new Set(),
642
696
  compile(classes) {
643
- if (designSystem.candidatesToAst) return designSystem.candidatesToAst(classes);
644
- if (designSystem.candidatesToCss) return designSystem.candidatesToCss(classes).map((css) => {
645
- if (!css) return [];
646
- try {
647
- return fromPostcss(postcss.default.parse(css).nodes);
648
- } catch {
649
- return [];
650
- }
651
- });
652
- return classes.map(() => []);
697
+ const missing = [...new Set(classes.filter((c) => !compileCache.has(c)))];
698
+ if (missing.length > 0) {
699
+ const compiled = compileUncached(missing);
700
+ for (const [index, className] of missing.entries()) compileCache.set(className, compiled[index] ?? []);
701
+ }
702
+ return classes.map((c) => compileCache.get(c) ?? []);
653
703
  }
654
704
  });
655
705
  state.designSystem = designSystem;
@@ -662,8 +712,7 @@ async function loadV4DesignSystem(state, resolvePaths, target, verbose = false)
662
712
  throw new Error(`Failed to load v4 design system: ${String(error)}`);
663
713
  }
664
714
  }
665
- //#endregion
666
- //#region src/utils/config.ts
715
+ // src/utils/config.ts
667
716
  const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
668
717
  const CONFIG_DISCOVERY_MAX_DEPTH = 8;
669
718
  const PROJECT_ROOT_MARKERS = ["package.json", ".git"];
@@ -838,8 +887,7 @@ function isPreferredCssFolder(relativePath) {
838
887
  function splitDepth(value) {
839
888
  return value.split(/[\\/]/).filter(Boolean).length;
840
889
  }
841
- //#endregion
842
- //#region src/state.ts
890
+ // src/state.ts
843
891
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
844
892
  function getTailwindVersion(paths) {
845
893
  try {
@@ -916,8 +964,7 @@ async function createState(cwd, target, verbose = false) {
916
964
  else await loadV3ClassMetadata(state, resolvePaths, verbose);
917
965
  return state;
918
966
  }
919
- //#endregion
920
- //#region src/linter.ts
967
+ // src/linter.ts
921
968
  const SOURCE_FILE_PATTERN = "**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}";
922
969
  const GLOB_PATTERN_REGEX = /[*?[\]{}]/;
923
970
  const MAX_LISTED_PACKAGES = 10;
@@ -1019,37 +1066,53 @@ function extractImportSourceDirectives(cssContent) {
1019
1066
  disableAutoSource
1020
1067
  };
1021
1068
  }
1022
- async function processFiles(state, cwd, files, fix, onProgress) {
1023
- const results = [];
1024
- for (let i = 0; i < files.length; i += 10) {
1025
- const batch = files.slice(i, i + 10);
1026
- const batchResults = await Promise.all(batch.map(async (file, batchIndex) => {
1027
- if (onProgress) onProgress(i + batchIndex + 1, files.length, file);
1028
- return processFile(state, cwd, file, fix);
1029
- }));
1030
- results.push(...batchResults.filter((r) => r !== null));
1069
+ async function processFiles(state, cwd, files, fix, dryRun, onProgress) {
1070
+ const results = Array.from({ length: files.length });
1071
+ let next = 0;
1072
+ let started = 0;
1073
+ const worker = async () => {
1074
+ while (next < files.length) {
1075
+ const index = next++;
1076
+ const file = files[index];
1077
+ onProgress?.(++started, files.length, file);
1078
+ results[index] = await processFile(state, cwd, file, fix, dryRun);
1079
+ }
1080
+ };
1081
+ await Promise.all(Array.from({ length: Math.min(10, files.length) }, worker));
1082
+ return results.filter((result) => result !== null);
1083
+ }
1084
+ async function readFileIfExists(filePath) {
1085
+ try {
1086
+ return await node_fs_promises.readFile(filePath, "utf-8");
1087
+ } catch {
1088
+ return null;
1031
1089
  }
1032
- return results;
1033
1090
  }
1034
- async function processFile(state, cwd, filePath, fix) {
1091
+ async function processFile(state, cwd, filePath, fix, dryRun) {
1035
1092
  const absolutePath = node_path.isAbsolute(filePath) ? filePath : node_path.resolve(cwd, filePath);
1036
- if (!fileExists(absolutePath)) return null;
1037
- const content = readFileSync(absolutePath);
1093
+ const content = await readFileIfExists(absolutePath);
1094
+ if (content === null) return null;
1038
1095
  let diagnostics = await validateDocument(state, absolutePath, content);
1039
1096
  let fixedCount = 0;
1097
+ let fixedContent;
1040
1098
  if (fix && diagnostics.length > 0) {
1041
1099
  const fixResult = await applyCodeActions(state, absolutePath, content, diagnostics);
1042
1100
  if (fixResult.changed) {
1043
- writeFileSync(absolutePath, fixResult.content);
1101
+ if (dryRun) fixedContent = fixResult.content;
1102
+ else await node_fs_promises.writeFile(absolutePath, fixResult.content, "utf-8");
1044
1103
  fixedCount = fixResult.fixedCount;
1045
- diagnostics = await validateDocument(state, absolutePath, fixResult.content);
1104
+ diagnostics = fixResult.diagnostics;
1046
1105
  }
1047
1106
  }
1048
1107
  return {
1049
1108
  path: node_path.relative(cwd, absolutePath),
1050
1109
  diagnostics,
1051
1110
  fixed: fixedCount > 0,
1052
- fixedCount
1111
+ fixedCount,
1112
+ ...fixedContent !== void 0 && {
1113
+ originalContent: content,
1114
+ fixedContent
1115
+ }
1053
1116
  };
1054
1117
  }
1055
1118
  async function initializeState(cwd, target, verbose = false) {
@@ -1062,7 +1125,7 @@ async function initializeState(cwd, target, verbose = false) {
1062
1125
  throw new Error(`Failed to initialize Tailwind state: ${message}`);
1063
1126
  }
1064
1127
  }
1065
- async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
1128
+ async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, dryRun = false, verbose = false, onProgress }) {
1066
1129
  const targets = await resolveConfigTargets(cwd, configPath);
1067
1130
  if (verbose) logTargets(cwd, targets);
1068
1131
  const configRoots = targets.map((target) => target.root);
@@ -1100,7 +1163,7 @@ async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verb
1100
1163
  if (!files) continue;
1101
1164
  const offset = processedCount;
1102
1165
  processedCount += files.length;
1103
- const processed = await processFiles(await initializeState(cwd, target, verbose), cwd, files.sort((a, b) => a.localeCompare(b)), fix, onProgress && ((current, _total, file) => onProgress(offset + current, totalFiles, file)));
1166
+ const processed = await processFiles(await initializeState(cwd, target, verbose), cwd, files.sort((a, b) => a.localeCompare(b)), fix || dryRun, dryRun, onProgress && ((current, _total, file) => onProgress(offset + current, totalFiles, file)));
1104
1167
  results.push(...processed);
1105
1168
  }
1106
1169
  return {
@@ -1138,7 +1201,6 @@ function logTargets(cwd, targets) {
1138
1201
  function ownershipScore(target, absolutePath) {
1139
1202
  return isInsideDir(target.root, absolutePath) ? target.root.length : -1;
1140
1203
  }
1141
- //#endregion
1142
1204
  Object.defineProperty(exports, "DEFAULT_FILE_PATTERN", {
1143
1205
  enumerable: true,
1144
1206
  get: function() {
package/dist/linter.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_linter = require("./linter-B-Bi_uRJ.cjs");
2
+ const require_linter = require("./linter-CUpiAi7p.cjs");
3
3
  exports.extractImportSourceDirectives = require_linter.extractImportSourceDirectives;
4
4
  exports.extractSourcePatterns = require_linter.extractSourcePatterns;
5
5
  exports.lint = require_linter.lint;
package/dist/linter.d.cts CHANGED
@@ -1,4 +1,3 @@
1
- //#region node_modules/.pnpm/vscode-languageserver-types@3.18.4/node_modules/vscode-languageserver-types/lib/esm/main.d.ts
2
1
  /**
3
2
  * A tagging type for string properties that are actually document URIs.
4
3
  */
@@ -430,13 +429,15 @@ declare namespace MarkupContent {
430
429
  */
431
430
  function is(value: any): value is MarkupContent;
432
431
  }
433
- //#endregion
434
- //#region src/types.d.ts
435
432
  interface LintFileResult {
436
433
  path: string;
437
434
  diagnostics: Diagnostic[];
438
435
  fixed?: boolean;
439
436
  fixedCount?: number;
437
+ /** Content before fixing; only set in dry-run mode when fixes are available */
438
+ originalContent?: string;
439
+ /** Content after fixing; only set in dry-run mode when fixes are available */
440
+ fixedContent?: string;
440
441
  }
441
442
  interface LintOptions {
442
443
  cwd: string;
@@ -444,6 +445,8 @@ interface LintOptions {
444
445
  configPath?: string;
445
446
  autoDiscover: boolean;
446
447
  fix?: boolean;
448
+ /** Compute fixes without writing them to disk (implies `fix`) */
449
+ dryRun?: boolean;
447
450
  verbose?: boolean;
448
451
  onProgress?: (current: number, total: number, file: string) => void;
449
452
  }
@@ -452,8 +455,6 @@ interface LintResult {
452
455
  totalFilesProcessed: number;
453
456
  skippedFiles: string[];
454
457
  }
455
- //#endregion
456
- //#region src/linter.d.ts
457
458
  declare function extractSourcePatterns(cssContent: string): {
458
459
  include: string[];
459
460
  exclude: string[];
@@ -462,6 +463,5 @@ declare function extractImportSourceDirectives(cssContent: string): {
462
463
  roots: string[];
463
464
  disableAutoSource: boolean;
464
465
  };
465
- export declare function lint({ cwd, patterns, configPath, autoDiscover, fix, verbose, onProgress }: LintOptions): Promise<LintResult>;
466
- //#endregion
466
+ export declare function lint({ cwd, patterns, configPath, autoDiscover, fix, dryRun, verbose, onProgress }: LintOptions): Promise<LintResult>;
467
467
  export { type LintFileResult, type LintOptions, type LintResult, extractImportSourceDirectives, extractSourcePatterns };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwind-lint",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "A command-line tool that uses the Tailwind CSS IntelliSense plugin to show linting suggestions for your Tailwind CSS classes",
5
5
  "keywords": [
6
6
  "cli",
@@ -61,22 +61,22 @@
61
61
  "@tailwindcss/language-service": "^0.16.0",
62
62
  "ansis": "^4.4.0",
63
63
  "commander": "^15.0.0",
64
- "postcss": "^8.5.29",
64
+ "postcss": "^8.5.32",
65
65
  "tinyglobby": "^0.2.17",
66
- "vscode-languageserver-textdocument": "^1.0.15"
66
+ "vscode-languageserver-textdocument": "^1.0.16"
67
67
  },
68
68
  "devDependencies": {
69
- "@types/node": "^26.6.4",
70
- "oxfmt": "^0.72.0",
71
- "oxlint": "^1.87.0",
72
- "tsdown": "^0.23.0",
69
+ "@types/node": "^26.6.5",
70
+ "oxfmt": "^0.75.0",
71
+ "oxlint": "^1.90.0",
72
+ "tsdown": "^0.23.2",
73
73
  "typescript": "^7.0.2",
74
- "vitest": "^5.0.3",
75
- "vscode-languageserver": "10.1.2"
74
+ "vitest": "^5.0.5",
75
+ "vscode-languageserver": "11.0.0"
76
76
  },
77
77
  "engines": {
78
78
  "node": ">=22.0.0",
79
79
  "pnpm": ">=10.0.0"
80
80
  },
81
- "packageManager": "pnpm@12.9.1"
81
+ "packageManager": "pnpm@12.15.0"
82
82
  }