tailwind-lint 0.7.0 → 0.8.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 +47 -0
- package/dist/cli.cjs +94 -14
- package/dist/linter.cjs +27 -3
- package/dist/linter.d.cts +5 -1
- package/dist/{state-BHl8x2Q1.cjs → state-QQi4_-5a.cjs} +54 -0
- package/package.json +80 -76
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ tailwind-lint --verbose
|
|
|
65
65
|
- `-c, --config <path>` - Path to Tailwind config file (default: auto-discover)
|
|
66
66
|
- `-a, --auto` - Auto-discover files from config content patterns (legacy, enabled by default)
|
|
67
67
|
- `--fix` - Automatically fix problems that can be fixed
|
|
68
|
+
- `--format <text|json>` - Output format (`text` default, `json` for machine-readable output)
|
|
68
69
|
- `-v, --verbose` - Enable verbose logging for debugging
|
|
69
70
|
- `-h, --help` - Show help message
|
|
70
71
|
- `--version` - Show version number
|
|
@@ -83,8 +84,33 @@ tailwind-lint "**/*.vue" --fix
|
|
|
83
84
|
|
|
84
85
|
# Lint with a specific CSS config (v4)
|
|
85
86
|
tailwind-lint --config ./styles/app.css
|
|
87
|
+
|
|
88
|
+
# Machine-readable output for LLMs/agents
|
|
89
|
+
tailwind-lint --auto --format json
|
|
86
90
|
```
|
|
87
91
|
|
|
92
|
+
## LLM / Agent Integration
|
|
93
|
+
|
|
94
|
+
Use JSON output to avoid brittle text parsing:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
tailwind-lint --auto --format json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The JSON payload includes:
|
|
101
|
+
|
|
102
|
+
- `ok` - `true` when no errors are found
|
|
103
|
+
- `summary` - counts for `errors`, `warnings`, `fixed`, `filesWithIssues`, `totalFilesProcessed`
|
|
104
|
+
- `config` - resolved runtime values (`cwd`, `configPath`, `autoDiscover`, `fix`, `patterns`)
|
|
105
|
+
- `files[]` - per-file diagnostics with 1-based `line`/`column` ranges
|
|
106
|
+
|
|
107
|
+
Typical agent flow:
|
|
108
|
+
|
|
109
|
+
1. Run `tailwind-lint --auto --format json`.
|
|
110
|
+
2. If `summary.errors > 0`, fail the check and surface diagnostics.
|
|
111
|
+
3. If only warnings exist, optionally continue and open a cleanup task.
|
|
112
|
+
4. Re-run with `--fix` when autofix is allowed.
|
|
113
|
+
|
|
88
114
|
## Configuration
|
|
89
115
|
|
|
90
116
|
### Tailwind CSS v4
|
|
@@ -172,4 +198,25 @@ pnpm format
|
|
|
172
198
|
|
|
173
199
|
# Check code without fixing
|
|
174
200
|
pnpm lint
|
|
201
|
+
|
|
202
|
+
# Preview next version locally (no publish)
|
|
203
|
+
pnpm release:dry
|
|
175
204
|
```
|
|
205
|
+
|
|
206
|
+
## Releases
|
|
207
|
+
|
|
208
|
+
Releases are automated with Semantic Release on pushes to `main`.
|
|
209
|
+
|
|
210
|
+
- Version bump is derived from Conventional Commits.
|
|
211
|
+
- npm release and GitHub release are generated automatically.
|
|
212
|
+
- npm publish uses npm Trusted Publishing (OIDC), no `NPM_TOKEN` required.
|
|
213
|
+
|
|
214
|
+
Commit examples:
|
|
215
|
+
|
|
216
|
+
- `feat: add json output mode` -> minor release
|
|
217
|
+
- `fix: resolve v4 config discovery in monorepos` -> patch release
|
|
218
|
+
- `feat: drop Node 20 support` + commit body `BREAKING CHANGE: Node 20 is no longer supported` -> major release
|
|
219
|
+
- `perf: speed up config discovery` -> patch release
|
|
220
|
+
- `docs: update readme` -> no release
|
|
221
|
+
|
|
222
|
+
`pnpm release:dry` runs against the local repo metadata (`--repository-url .`) so it does not require GitHub remote access.
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_state = require('./state-
|
|
2
|
+
const require_state = require('./state-QQi4_-5a.cjs');
|
|
3
3
|
const require_linter = require('./linter.cjs');
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_state.__toESM(node_path);
|
|
@@ -9,9 +9,8 @@ let node_fs = require("node:fs");
|
|
|
9
9
|
node_fs = require_state.__toESM(node_fs);
|
|
10
10
|
let commander = require("commander");
|
|
11
11
|
|
|
12
|
-
//#region src/
|
|
13
|
-
|
|
14
|
-
function countDiagnosticsBySeverity(diagnostics) {
|
|
12
|
+
//#region src/output.ts
|
|
13
|
+
function countBySeverity(diagnostics) {
|
|
15
14
|
let errors = 0;
|
|
16
15
|
let warnings = 0;
|
|
17
16
|
for (const diagnostic of diagnostics) {
|
|
@@ -23,6 +22,64 @@ function countDiagnosticsBySeverity(diagnostics) {
|
|
|
23
22
|
warnings
|
|
24
23
|
};
|
|
25
24
|
}
|
|
25
|
+
function toJsonSeverity(severity) {
|
|
26
|
+
if (severity === require_state.SEVERITY.ERROR) return "error";
|
|
27
|
+
if (severity === require_state.SEVERITY.WARNING) return "warning";
|
|
28
|
+
return "info";
|
|
29
|
+
}
|
|
30
|
+
function toJsonDiagnostic(diagnostic) {
|
|
31
|
+
return {
|
|
32
|
+
line: diagnostic.range.start.line + 1,
|
|
33
|
+
column: diagnostic.range.start.character + 1,
|
|
34
|
+
endLine: diagnostic.range.end.line + 1,
|
|
35
|
+
endColumn: diagnostic.range.end.character + 1,
|
|
36
|
+
severity: toJsonSeverity(diagnostic.severity),
|
|
37
|
+
code: diagnostic.code ?? null,
|
|
38
|
+
message: diagnostic.message,
|
|
39
|
+
source: diagnostic.source
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createJsonOutput({ files, totalFilesProcessed, cwd, configPath, autoDiscover, fix, patterns }) {
|
|
43
|
+
let errors = 0;
|
|
44
|
+
let warnings = 0;
|
|
45
|
+
let fixed = 0;
|
|
46
|
+
let filesWithIssues = 0;
|
|
47
|
+
const mappedFiles = files.map((file) => {
|
|
48
|
+
const severityCount = countBySeverity(file.diagnostics);
|
|
49
|
+
errors += severityCount.errors;
|
|
50
|
+
warnings += severityCount.warnings;
|
|
51
|
+
fixed += file.fixedCount || 0;
|
|
52
|
+
if (file.diagnostics.length > 0) filesWithIssues++;
|
|
53
|
+
return {
|
|
54
|
+
path: file.path,
|
|
55
|
+
fixed: file.fixed || false,
|
|
56
|
+
fixedCount: file.fixedCount || 0,
|
|
57
|
+
diagnostics: file.diagnostics.map(toJsonDiagnostic)
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
ok: errors === 0,
|
|
62
|
+
summary: {
|
|
63
|
+
errors,
|
|
64
|
+
warnings,
|
|
65
|
+
fixed,
|
|
66
|
+
filesWithIssues,
|
|
67
|
+
totalFilesProcessed
|
|
68
|
+
},
|
|
69
|
+
config: {
|
|
70
|
+
cwd,
|
|
71
|
+
configPath: configPath || null,
|
|
72
|
+
autoDiscover,
|
|
73
|
+
fix,
|
|
74
|
+
patterns
|
|
75
|
+
},
|
|
76
|
+
files: mappedFiles
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/cli.ts
|
|
82
|
+
const MAX_FILENAME_DISPLAY_LENGTH = 50;
|
|
26
83
|
function resolveOptions(files, options) {
|
|
27
84
|
const hasConfigFlag = !!options.config;
|
|
28
85
|
const hasAutoFlag = !!options.auto;
|
|
@@ -66,7 +123,7 @@ async function displayResults(files, fixMode) {
|
|
|
66
123
|
console.log(chalk.default.green(` ✔ Fixed ${issueText}`));
|
|
67
124
|
totalFixed += file.fixedCount || 0;
|
|
68
125
|
}
|
|
69
|
-
const { errors, warnings } =
|
|
126
|
+
const { errors, warnings } = countBySeverity(file.diagnostics);
|
|
70
127
|
totalErrors += errors;
|
|
71
128
|
totalWarnings += warnings;
|
|
72
129
|
if (file.diagnostics.length > 0) filesWithIssues++;
|
|
@@ -132,7 +189,7 @@ program.configureHelp({ formatHelp: (cmd, helper) => {
|
|
|
132
189
|
}
|
|
133
190
|
return output;
|
|
134
191
|
} });
|
|
135
|
-
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 content patterns").option("--fix", "Automatically fix problems that can be fixed").option("-v, --verbose", "Enable verbose logging for debugging").addHelpText("after", `
|
|
192
|
+
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 content 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", `
|
|
136
193
|
${chalk.default.bold.cyan("Examples:")}
|
|
137
194
|
${chalk.default.dim("$")} tailwind-lint
|
|
138
195
|
${chalk.default.dim("$")} tailwind-lint ${chalk.default.green("\"src/**/*.{js,jsx,ts,tsx}\"")}
|
|
@@ -151,8 +208,9 @@ ${chalk.default.bold.cyan("Notes:")}
|
|
|
151
208
|
const hasAutoFlag = !!options.auto;
|
|
152
209
|
if (!(files.length > 0) && !hasAutoFlag && !hasConfigFlag) options.auto = true;
|
|
153
210
|
const resolved = resolveOptions(files, options);
|
|
211
|
+
const isJsonOutput = (options.format === "json" ? "json" : "text") === "json";
|
|
154
212
|
try {
|
|
155
|
-
if (resolved.verbose) {
|
|
213
|
+
if (resolved.verbose && !isJsonOutput) {
|
|
156
214
|
console.log(chalk.default.cyan.bold("→ Configuration"));
|
|
157
215
|
console.log(chalk.default.dim(` Working directory: ${resolved.cwd}`));
|
|
158
216
|
console.log(chalk.default.dim(` Config path: ${resolved.configPath || "auto-discover"}`));
|
|
@@ -163,29 +221,51 @@ ${chalk.default.bold.cyan("Notes:")}
|
|
|
163
221
|
const results = await require_linter.lint({
|
|
164
222
|
...resolved,
|
|
165
223
|
onProgress: (current, total, file) => {
|
|
224
|
+
if (isJsonOutput) return;
|
|
166
225
|
if (process.stdout.isTTY && !resolved.verbose) {
|
|
167
226
|
const displayFile = truncateFilename(file);
|
|
168
227
|
process.stdout.write(`\r${chalk.default.cyan("→")} Linting files... ${chalk.default.dim(`(${current}/${total})`)} ${chalk.default.dim(displayFile)}${" ".repeat(require_state.TERMINAL_PADDING)}`);
|
|
169
228
|
} else if (resolved.verbose) console.log(chalk.default.dim(` [${current}/${total}] Linting ${file}`));
|
|
170
229
|
}
|
|
171
230
|
});
|
|
172
|
-
if (process.stdout.isTTY && !resolved.verbose) process.stdout.write(`\r${" ".repeat(require_state.TERMINAL_WIDTH)}\r`);
|
|
231
|
+
if (process.stdout.isTTY && !resolved.verbose && !isJsonOutput) process.stdout.write(`\r${" ".repeat(require_state.TERMINAL_WIDTH)}\r`);
|
|
173
232
|
if (results.totalFilesProcessed === 0) {
|
|
174
|
-
console.log(
|
|
175
|
-
|
|
233
|
+
if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
|
|
234
|
+
...resolved,
|
|
235
|
+
files: [],
|
|
236
|
+
totalFilesProcessed: 0
|
|
237
|
+
})));
|
|
238
|
+
else {
|
|
239
|
+
console.log();
|
|
240
|
+
console.log(chalk.default.yellow("⚠ No files found to lint"));
|
|
241
|
+
}
|
|
176
242
|
process.exit(0);
|
|
177
243
|
}
|
|
178
244
|
if (results.files.length === 0) {
|
|
179
|
-
console.log(
|
|
245
|
+
if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
|
|
246
|
+
...resolved,
|
|
247
|
+
files: [],
|
|
248
|
+
totalFilesProcessed: results.totalFilesProcessed
|
|
249
|
+
})));
|
|
250
|
+
else console.log(chalk.default.green.bold("✔ No issues found"));
|
|
180
251
|
process.exit(0);
|
|
181
252
|
}
|
|
182
|
-
|
|
253
|
+
if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
|
|
254
|
+
...resolved,
|
|
255
|
+
files: results.files,
|
|
256
|
+
totalFilesProcessed: results.totalFilesProcessed
|
|
257
|
+
})));
|
|
258
|
+
else await displayResults(results.files, resolved.fix);
|
|
183
259
|
const hasErrors = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_state.SEVERITY.ERROR));
|
|
184
260
|
process.exit(hasErrors ? 1 : 0);
|
|
185
261
|
} catch (error) {
|
|
186
262
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
187
|
-
console.
|
|
188
|
-
|
|
263
|
+
if (isJsonOutput) console.log(JSON.stringify({
|
|
264
|
+
ok: false,
|
|
265
|
+
error: errorMessage
|
|
266
|
+
}));
|
|
267
|
+
else console.error(chalk.default.red("✖ Error:"), errorMessage);
|
|
268
|
+
if (resolved.verbose && error instanceof Error && !isJsonOutput) {
|
|
189
269
|
console.error(chalk.default.dim("\nStack trace:"));
|
|
190
270
|
console.error(chalk.default.dim(error.stack || error.toString()));
|
|
191
271
|
}
|
package/dist/linter.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_state = require('./state-
|
|
2
|
+
const require_state = require('./state-QQi4_-5a.cjs');
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
node_path = require_state.__toESM(node_path);
|
|
5
5
|
let _tailwindcss_language_service = require("@tailwindcss/language-service");
|
|
@@ -33,11 +33,12 @@ async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
|
|
|
33
33
|
return expandPatterns(cwd, patterns);
|
|
34
34
|
}
|
|
35
35
|
async function expandPatterns(cwd, patterns, extraIgnore = []) {
|
|
36
|
-
|
|
36
|
+
const files = await (0, fast_glob.default)(patterns, {
|
|
37
37
|
cwd,
|
|
38
38
|
absolute: false,
|
|
39
39
|
ignore: [...require_state.DEFAULT_IGNORE_PATTERNS, ...extraIgnore]
|
|
40
40
|
});
|
|
41
|
+
return [...new Set(files)].sort((a, b) => a.localeCompare(b));
|
|
41
42
|
}
|
|
42
43
|
async function discoverFilesFromConfig(cwd, configPath) {
|
|
43
44
|
const configFilePath = await require_state.findTailwindConfigPath(cwd, configPath);
|
|
@@ -50,7 +51,9 @@ async function discoverFilesFromConfig(cwd, configPath) {
|
|
|
50
51
|
return expandPatterns(cwd, patterns);
|
|
51
52
|
}
|
|
52
53
|
const configDir = node_path.dirname(configFilePath);
|
|
53
|
-
const
|
|
54
|
+
const cssContent = require_state.readFileSync(configFilePath);
|
|
55
|
+
const { include, exclude } = extractSourcePatterns(cssContent);
|
|
56
|
+
const importSource = extractImportSourceDirectives(cssContent);
|
|
54
57
|
const resolveFromConfig = (pattern) => {
|
|
55
58
|
const absolutePattern = node_path.resolve(configDir, pattern);
|
|
56
59
|
return node_path.relative(cwd, absolutePattern);
|
|
@@ -59,6 +62,8 @@ async function discoverFilesFromConfig(cwd, configPath) {
|
|
|
59
62
|
const gitignorePatterns = require_state.readGitignorePatterns(cwd);
|
|
60
63
|
const extraIgnore = [...resolvedExclude, ...gitignorePatterns];
|
|
61
64
|
if (include.length > 0) return expandPatterns(cwd, include.map(resolveFromConfig), extraIgnore);
|
|
65
|
+
if (importSource.roots.length > 0) return expandPatterns(cwd, importSource.roots.map((root) => resolveFromConfig(node_path.join(root, "**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}"))), extraIgnore);
|
|
66
|
+
if (importSource.disableAutoSource) return [];
|
|
62
67
|
return expandPatterns(cwd, [require_state.DEFAULT_FILE_PATTERN], extraIgnore);
|
|
63
68
|
}
|
|
64
69
|
function extractContentPatterns(config) {
|
|
@@ -80,6 +85,24 @@ function extractSourcePatterns(cssContent) {
|
|
|
80
85
|
exclude
|
|
81
86
|
};
|
|
82
87
|
}
|
|
88
|
+
function extractImportSourceDirectives(cssContent) {
|
|
89
|
+
const roots = [];
|
|
90
|
+
let disableAutoSource = false;
|
|
91
|
+
for (const match of cssContent.matchAll(/@import\s+["']tailwindcss(?:[^;]*?)\ssource\(\s*(none|["'][^"']+["'])\s*\)/g)) {
|
|
92
|
+
const raw = match[1];
|
|
93
|
+
if (!raw) continue;
|
|
94
|
+
if (raw === "none") {
|
|
95
|
+
disableAutoSource = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const sourceRoot = raw.slice(1, -1).trim();
|
|
99
|
+
if (sourceRoot.length > 0) roots.push(sourceRoot);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
roots: [...new Set(roots)],
|
|
103
|
+
disableAutoSource
|
|
104
|
+
};
|
|
105
|
+
}
|
|
83
106
|
async function processFiles(state, cwd, files, fix, onProgress) {
|
|
84
107
|
const results = [];
|
|
85
108
|
for (let i = 0; i < files.length; i += require_state.CONCURRENT_FILES) {
|
|
@@ -141,5 +164,6 @@ async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verb
|
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
//#endregion
|
|
167
|
+
exports.extractImportSourceDirectives = extractImportSourceDirectives;
|
|
144
168
|
exports.extractSourcePatterns = extractSourcePatterns;
|
|
145
169
|
exports.lint = lint;
|
package/dist/linter.d.cts
CHANGED
|
@@ -26,6 +26,10 @@ declare function extractSourcePatterns(cssContent: string): {
|
|
|
26
26
|
include: string[];
|
|
27
27
|
exclude: string[];
|
|
28
28
|
};
|
|
29
|
+
declare function extractImportSourceDirectives(cssContent: string): {
|
|
30
|
+
roots: string[];
|
|
31
|
+
disableAutoSource: boolean;
|
|
32
|
+
};
|
|
29
33
|
declare function lint({
|
|
30
34
|
cwd,
|
|
31
35
|
patterns,
|
|
@@ -36,4 +40,4 @@ declare function lint({
|
|
|
36
40
|
onProgress
|
|
37
41
|
}: LintOptions): Promise<LintResult>;
|
|
38
42
|
//#endregion
|
|
39
|
-
export { type LintFileResult, type LintOptions, type LintResult, extractSourcePatterns, lint };
|
|
43
|
+
export { type LintFileResult, type LintOptions, type LintResult, extractImportSourceDirectives, extractSourcePatterns, lint };
|
|
@@ -30,6 +30,8 @@ node_path = __toESM(node_path);
|
|
|
30
30
|
let _tailwindcss_language_service = require("@tailwindcss/language-service");
|
|
31
31
|
let chalk = require("chalk");
|
|
32
32
|
chalk = __toESM(chalk);
|
|
33
|
+
let fast_glob = require("fast-glob");
|
|
34
|
+
fast_glob = __toESM(fast_glob);
|
|
33
35
|
let vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
|
|
34
36
|
let node_module = require("node:module");
|
|
35
37
|
let node_fs = require("node:fs");
|
|
@@ -483,6 +485,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
483
485
|
//#endregion
|
|
484
486
|
//#region src/utils/config.ts
|
|
485
487
|
const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
488
|
+
const CONFIG_DISCOVERY_MAX_DEPTH = 8;
|
|
486
489
|
const isCssConfigFile = (filePath) => filePath.endsWith(".css");
|
|
487
490
|
async function loadTailwindConfig(configPath) {
|
|
488
491
|
if (isCssConfigFile(configPath)) return {};
|
|
@@ -509,6 +512,13 @@ async function findTailwindConfigPath(cwd, configPath) {
|
|
|
509
512
|
const fullPath = node_path.join(cwd, p);
|
|
510
513
|
if (fileExists(fullPath)) return fullPath;
|
|
511
514
|
}
|
|
515
|
+
const v3Recursive = await (0, fast_glob.default)(V3_CONFIG_PATHS.map((p) => `**/${p}`), {
|
|
516
|
+
cwd,
|
|
517
|
+
absolute: true,
|
|
518
|
+
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
519
|
+
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
520
|
+
});
|
|
521
|
+
if (v3Recursive.length > 0) return sortByPathDepth(v3Recursive)[0];
|
|
512
522
|
const v4Paths = V4_CSS_FOLDERS.flatMap((folder) => V4_CSS_NAMES.map((name) => node_path.join(folder, name)));
|
|
513
523
|
for (const p of v4Paths) {
|
|
514
524
|
const fullPath = node_path.join(cwd, p);
|
|
@@ -517,8 +527,52 @@ async function findTailwindConfigPath(cwd, configPath) {
|
|
|
517
527
|
if (TAILWIND_V4_IMPORT_REGEX.test(content)) return fullPath;
|
|
518
528
|
} catch {}
|
|
519
529
|
}
|
|
530
|
+
const cssCandidates = await (0, fast_glob.default)("**/*.css", {
|
|
531
|
+
cwd,
|
|
532
|
+
absolute: true,
|
|
533
|
+
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
534
|
+
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
535
|
+
});
|
|
536
|
+
const v4Matches = [];
|
|
537
|
+
for (const candidate of cssCandidates) try {
|
|
538
|
+
const content = readFileSync(candidate);
|
|
539
|
+
if (TAILWIND_V4_IMPORT_REGEX.test(content)) v4Matches.push(candidate);
|
|
540
|
+
} catch {}
|
|
541
|
+
if (v4Matches.length > 0) return sortCssCandidates(cwd, v4Matches)[0];
|
|
520
542
|
return null;
|
|
521
543
|
}
|
|
544
|
+
function sortByPathDepth(paths) {
|
|
545
|
+
return [...paths].sort((a, b) => {
|
|
546
|
+
const depthA = splitDepth(a);
|
|
547
|
+
const depthB = splitDepth(b);
|
|
548
|
+
if (depthA !== depthB) return depthA - depthB;
|
|
549
|
+
return a.localeCompare(b);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function sortCssCandidates(cwd, paths) {
|
|
553
|
+
return [...paths].sort((a, b) => {
|
|
554
|
+
const scoreA = cssCandidateScore(cwd, a);
|
|
555
|
+
const scoreB = cssCandidateScore(cwd, b);
|
|
556
|
+
if (scoreA !== scoreB) return scoreA - scoreB;
|
|
557
|
+
return a.localeCompare(b);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function cssCandidateScore(cwd, candidate) {
|
|
561
|
+
const normalized = node_path.relative(cwd, candidate).split(node_path.sep).join("/");
|
|
562
|
+
const base = node_path.basename(candidate);
|
|
563
|
+
const depth = splitDepth(normalized);
|
|
564
|
+
const nameScore = V4_CSS_NAMES.includes(base) ? 0 : 20;
|
|
565
|
+
const folderScore = isPreferredCssFolder(normalized) ? 0 : 10;
|
|
566
|
+
return depth * 100 + nameScore + folderScore;
|
|
567
|
+
}
|
|
568
|
+
function isPreferredCssFolder(relativePath) {
|
|
569
|
+
const folder = node_path.dirname(relativePath).replace(/\\/g, "/");
|
|
570
|
+
const withSlash = folder === "." ? "./" : `./${folder}/`;
|
|
571
|
+
return V4_CSS_FOLDERS.includes(withSlash);
|
|
572
|
+
}
|
|
573
|
+
function splitDepth(value) {
|
|
574
|
+
return value.split(/[\\/]/).filter(Boolean).length;
|
|
575
|
+
}
|
|
522
576
|
|
|
523
577
|
//#endregion
|
|
524
578
|
//#region src/state.ts
|
package/package.json
CHANGED
|
@@ -1,77 +1,81 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
2
|
+
"name": "tailwind-lint",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "A command-line tool that uses the Tailwind CSS IntelliSense plugin to show linting suggestions for your Tailwind CSS classes",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"code-quality",
|
|
8
|
+
"css",
|
|
9
|
+
"diagnostics",
|
|
10
|
+
"intellisense",
|
|
11
|
+
"lint",
|
|
12
|
+
"linter",
|
|
13
|
+
"tailwind",
|
|
14
|
+
"tailwindcss",
|
|
15
|
+
"utility-first"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/ph1p/tailwind-lint#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/ph1p/tailwind-lint/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Philip Stapelfeldt <me@ph1p.dev>",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/ph1p/tailwind-lint.git"
|
|
26
|
+
},
|
|
27
|
+
"funding": {
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/ph1p"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"tailwind-lint": "./dist/cli.cjs"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"type": "module",
|
|
40
|
+
"types": "./dist/linter.d.ts",
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org/"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"dev": "tsdown --watch",
|
|
48
|
+
"format": "oxfmt --write .",
|
|
49
|
+
"format:check": "oxfmt --check .",
|
|
50
|
+
"lint": "oxlint .",
|
|
51
|
+
"lint:fix": "oxlint --fix .",
|
|
52
|
+
"prepublishOnly": "pnpm build && pnpm test",
|
|
53
|
+
"release": "pnpm dlx semantic-release",
|
|
54
|
+
"release:dry": "SEMREL_LOCAL=1 pnpm dlx semantic-release --dry-run --no-ci --repository-url .",
|
|
55
|
+
"start": "node dist/cli.cjs",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:coverage": "vitest run --coverage",
|
|
58
|
+
"test:watch": "vitest"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@tailwindcss/language-service": "^0.14.29",
|
|
62
|
+
"chalk": "^5.6.2",
|
|
63
|
+
"commander": "^14.0.3",
|
|
64
|
+
"fast-glob": "^3.3.3",
|
|
65
|
+
"postcss": "^8.5.6",
|
|
66
|
+
"vscode-languageserver-textdocument": "^1.0.12"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^25.3.0",
|
|
70
|
+
"oxfmt": "^0.34.0",
|
|
71
|
+
"oxlint": "^1.49.0",
|
|
72
|
+
"tsdown": "^0.20.3",
|
|
73
|
+
"typescript": "^5.9.3",
|
|
74
|
+
"vitest": "^4.0.18"
|
|
75
|
+
},
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=22.0.0",
|
|
78
|
+
"pnpm": ">=10.0.0"
|
|
79
|
+
},
|
|
80
|
+
"packageManager": "pnpm@10.30.1"
|
|
81
|
+
}
|