tailwind-lint 0.5.1 → 0.7.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
@@ -48,11 +48,15 @@ tailwind-lint --verbose
48
48
  ### How Auto-Discovery Works
49
49
 
50
50
  **Tailwind CSS v4:**
51
- - Finds CSS config files: `app.css`, `src/app.css`, `index.css`, `src/index.css`, `tailwind.css`, `src/tailwind.css`
51
+
52
+ - Finds CSS config files in common locations: `app.css`, `index.css`, `tailwind.css`, `global.css`, etc.
53
+ - Searches in project root and subdirectories: `./`, `./src/`, `./src/styles/`, `./app/`, etc.
52
54
  - Uses file patterns from `@source` directives if present
53
- - Falls back to default pattern: `./**/*.{js,jsx,ts,tsx,html}`
55
+ - Falls back to default pattern: `./**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}`
56
+ - **Note:** When CSS config is in a subdirectory (e.g., `src/styles/global.css`), files are discovered from the project root
54
57
 
55
58
  **Tailwind CSS v3:**
59
+
56
60
  - Finds JavaScript config files: `tailwind.config.js`, `tailwind.config.cjs`, `tailwind.config.mjs`, `tailwind.config.ts`
57
61
  - Uses file patterns from the `content` array in your config
58
62
 
@@ -91,7 +95,7 @@ Create a CSS config file (`app.css`, `index.css`, or `tailwind.css`):
91
95
  @import "tailwindcss";
92
96
 
93
97
  @theme {
94
- --color-primary: #3b82f6;
98
+ --color-primary: #3b82f6;
95
99
  }
96
100
 
97
101
  @source "./src/**/*.{js,jsx,ts,tsx,html}";
@@ -106,12 +110,12 @@ Create a JavaScript config file (`tailwind.config.js`):
106
110
  ```javascript
107
111
  /** @type {import('tailwindcss').Config} */
108
112
  module.exports = {
109
- content: ['./src/**/*.{js,jsx,ts,tsx}'],
110
- theme: {
111
- extend: {},
112
- },
113
- plugins: [],
114
- }
113
+ content: ["./src/**/*.{js,jsx,ts,tsx}"],
114
+ theme: {
115
+ extend: {},
116
+ },
117
+ plugins: [],
118
+ };
115
119
  ```
116
120
 
117
121
  ## Autofix
@@ -126,7 +130,8 @@ Files are written atomically with multiple iterations to ensure all fixes are ap
126
130
  ## Features
127
131
 
128
132
  **Core (v3 & v4):**
129
- - CSS Conflicts - Detects when multiple classes apply the same CSS properties
133
+
134
+ - CSS Conflicts - Detects when multiple classes apply the same CSS properties (e.g., `block flex`, `text-left text-center`) - **Note:** Works reliably in v4, limited support in v3 - no autofix
130
135
  - Invalid @apply Usage - Validates if a class can be used with `@apply`
131
136
  - Invalid @screen References - Detects references to non-existent breakpoints
132
137
  - Invalid Config Paths - Validates references in `config()` and `theme()` functions
@@ -136,6 +141,7 @@ Files are written atomically with multiple iterations to ensure all fixes are ap
136
141
  - Autofix - Automatically fix issues with `--fix` flag
137
142
 
138
143
  **v4-Specific:**
144
+
139
145
  - Canonical Class Suggestions - Suggests shorthand equivalents for arbitrary values (e.g., `top-[60px]` → `top-15`)
140
146
  - Invalid @source Directives - Validates `@source` directive paths
141
147
  - Full theme loading - Automatically loads Tailwind's default theme
package/dist/cli.cjs CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
- const require_linter = require('./linter-B1HK1nl2.cjs');
2
+ const require_state = require('./state-BHl8x2Q1.cjs');
3
+ const require_linter = require('./linter.cjs');
3
4
  let node_path = require("node:path");
4
- node_path = require_linter.__toESM(node_path);
5
+ node_path = require_state.__toESM(node_path);
5
6
  let chalk = require("chalk");
6
- chalk = require_linter.__toESM(chalk);
7
+ chalk = require_state.__toESM(chalk);
7
8
  let node_fs = require("node:fs");
8
- node_fs = require_linter.__toESM(node_fs);
9
+ node_fs = require_state.__toESM(node_fs);
9
10
  let commander = require("commander");
10
11
 
11
12
  //#region src/cli.ts
@@ -14,8 +15,8 @@ function countDiagnosticsBySeverity(diagnostics) {
14
15
  let errors = 0;
15
16
  let warnings = 0;
16
17
  for (const diagnostic of diagnostics) {
17
- if (diagnostic.severity === require_linter.SEVERITY.ERROR) errors++;
18
- if (diagnostic.severity === require_linter.SEVERITY.WARNING) warnings++;
18
+ if (diagnostic.severity === require_state.SEVERITY.ERROR) errors++;
19
+ if (diagnostic.severity === require_state.SEVERITY.WARNING) warnings++;
19
20
  }
20
21
  return {
21
22
  errors,
@@ -33,7 +34,7 @@ function resolveOptions(files, options) {
33
34
  const absoluteConfigPath = node_path.isAbsolute(options.config) ? options.config : node_path.resolve(process.cwd(), options.config);
34
35
  cwd = node_path.dirname(absoluteConfigPath);
35
36
  configPath = node_path.basename(absoluteConfigPath);
36
- patterns = [require_linter.DEFAULT_FILE_PATTERN];
37
+ patterns = [require_state.DEFAULT_FILE_PATTERN];
37
38
  }
38
39
  const autoDiscover = hasAutoFlag;
39
40
  return {
@@ -72,8 +73,8 @@ async function displayResults(files, fixMode) {
72
73
  for (const diagnostic of file.diagnostics) {
73
74
  const line = diagnostic.range.start.line + 1;
74
75
  const char = diagnostic.range.start.character + 1;
75
- const severity = diagnostic.severity === require_linter.SEVERITY.ERROR ? "error" : "warning";
76
- const severityColor = diagnostic.severity === require_linter.SEVERITY.ERROR ? chalk.default.red(severity) : chalk.default.yellow(severity);
76
+ const severity = diagnostic.severity === require_state.SEVERITY.ERROR ? "error" : "warning";
77
+ const severityColor = diagnostic.severity === require_state.SEVERITY.ERROR ? chalk.default.red(severity) : chalk.default.yellow(severity);
77
78
  const code = diagnostic.code ? chalk.default.dim(` (${diagnostic.code})`) : "";
78
79
  console.log(` ${chalk.default.dim(`${line}:${char}`)} ${severityColor} ${diagnostic.message}${code}`);
79
80
  }
@@ -100,9 +101,9 @@ const program = new commander.Command();
100
101
  const getVersion = () => {
101
102
  const packageJsonPath = node_path.join(__dirname, "../package.json");
102
103
  try {
103
- return JSON.parse(node_fs.readFileSync(packageJsonPath, "utf-8")).version || require_linter.DEFAULT_VERSION;
104
+ return JSON.parse(node_fs.readFileSync(packageJsonPath, "utf-8")).version || require_state.DEFAULT_VERSION;
104
105
  } catch {
105
- return require_linter.DEFAULT_VERSION;
106
+ return require_state.DEFAULT_VERSION;
106
107
  }
107
108
  };
108
109
  program.configureHelp({ formatHelp: (cmd, helper) => {
@@ -164,11 +165,11 @@ ${chalk.default.bold.cyan("Notes:")}
164
165
  onProgress: (current, total, file) => {
165
166
  if (process.stdout.isTTY && !resolved.verbose) {
166
167
  const displayFile = truncateFilename(file);
167
- process.stdout.write(`\r${chalk.default.cyan("→")} Linting files... ${chalk.default.dim(`(${current}/${total})`)} ${chalk.default.dim(displayFile)}${" ".repeat(require_linter.TERMINAL_PADDING)}`);
168
+ process.stdout.write(`\r${chalk.default.cyan("→")} Linting files... ${chalk.default.dim(`(${current}/${total})`)} ${chalk.default.dim(displayFile)}${" ".repeat(require_state.TERMINAL_PADDING)}`);
168
169
  } else if (resolved.verbose) console.log(chalk.default.dim(` [${current}/${total}] Linting ${file}`));
169
170
  }
170
171
  });
171
- if (process.stdout.isTTY && !resolved.verbose) process.stdout.write(`\r${" ".repeat(require_linter.TERMINAL_WIDTH)}\r`);
172
+ if (process.stdout.isTTY && !resolved.verbose) process.stdout.write(`\r${" ".repeat(require_state.TERMINAL_WIDTH)}\r`);
172
173
  if (results.totalFilesProcessed === 0) {
173
174
  console.log();
174
175
  console.log(chalk.default.yellow("⚠ No files found to lint"));
@@ -179,7 +180,7 @@ ${chalk.default.bold.cyan("Notes:")}
179
180
  process.exit(0);
180
181
  }
181
182
  await displayResults(results.files, resolved.fix);
182
- const hasErrors = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_linter.SEVERITY.ERROR));
183
+ const hasErrors = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_state.SEVERITY.ERROR));
183
184
  process.exit(hasErrors ? 1 : 0);
184
185
  } catch (error) {
185
186
  const errorMessage = error instanceof Error ? error.message : String(error);
package/dist/linter.cjs CHANGED
@@ -1,3 +1,145 @@
1
- const require_linter = require('./linter-B1HK1nl2.cjs');
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_state = require('./state-BHl8x2Q1.cjs');
3
+ let node_path = require("node:path");
4
+ node_path = require_state.__toESM(node_path);
5
+ let _tailwindcss_language_service = require("@tailwindcss/language-service");
6
+ let chalk = require("chalk");
7
+ chalk = require_state.__toESM(chalk);
8
+ let fast_glob = require("fast-glob");
9
+ fast_glob = require_state.__toESM(fast_glob);
10
+ let vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
2
11
 
3
- exports.lint = require_linter.lint;
12
+ //#region src/linter.ts
13
+ async function validateDocument(state, filePath, content) {
14
+ try {
15
+ if (!state) throw new Error("State is not initialized");
16
+ if (state.v4 && !state.designSystem) throw new Error("Design system not initialized for Tailwind v4. This might indicate a configuration issue.");
17
+ if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
18
+ const languageId = require_state.getLanguageId(filePath);
19
+ const uri = `file://${filePath}`;
20
+ return await (0, _tailwindcss_language_service.doValidate)(state, vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content));
21
+ } catch (error) {
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ if (message.includes("Cannot read") || message.includes("undefined")) {
24
+ if (process.env.DEBUG) console.error(`Debug: Language service error for ${filePath}:`, error);
25
+ console.warn(`Warning: Language service crashed while validating ${filePath}. Skipping this file.`);
26
+ return [];
27
+ }
28
+ throw new Error(`Failed to validate document ${filePath}: ${message}`);
29
+ }
30
+ }
31
+ async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
32
+ if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
33
+ return expandPatterns(cwd, patterns);
34
+ }
35
+ async function expandPatterns(cwd, patterns, extraIgnore = []) {
36
+ return (0, fast_glob.default)(patterns, {
37
+ cwd,
38
+ absolute: false,
39
+ ignore: [...require_state.DEFAULT_IGNORE_PATTERNS, ...extraIgnore]
40
+ });
41
+ }
42
+ async function discoverFilesFromConfig(cwd, configPath) {
43
+ const configFilePath = await require_state.findTailwindConfigPath(cwd, configPath);
44
+ if (!configFilePath) throw new Error("Could not find Tailwind config for auto-discovery.\nUse --config to specify the path, or provide file patterns directly.");
45
+ if (!require_state.isCssConfigFile(configFilePath)) {
46
+ const config = await require_state.loadTailwindConfig(configFilePath);
47
+ if (!config || !config.content) throw new Error("Tailwind config is missing the 'content' property.\nAdd a content array to specify which files to scan:\n content: ['./src/**/*.{js,jsx,ts,tsx}']");
48
+ const patterns = extractContentPatterns(config);
49
+ if (patterns.length === 0) throw new Error("No content patterns found in Tailwind config.\nEnsure your config has a content array with file patterns.");
50
+ return expandPatterns(cwd, patterns);
51
+ }
52
+ const configDir = node_path.dirname(configFilePath);
53
+ const { include, exclude } = extractSourcePatterns(require_state.readFileSync(configFilePath));
54
+ const resolveFromConfig = (pattern) => {
55
+ const absolutePattern = node_path.resolve(configDir, pattern);
56
+ return node_path.relative(cwd, absolutePattern);
57
+ };
58
+ const resolvedExclude = exclude.map(resolveFromConfig);
59
+ const gitignorePatterns = require_state.readGitignorePatterns(cwd);
60
+ const extraIgnore = [...resolvedExclude, ...gitignorePatterns];
61
+ if (include.length > 0) return expandPatterns(cwd, include.map(resolveFromConfig), extraIgnore);
62
+ return expandPatterns(cwd, [require_state.DEFAULT_FILE_PATTERN], extraIgnore);
63
+ }
64
+ function extractContentPatterns(config) {
65
+ if (!config.content) return [];
66
+ return (Array.isArray(config.content) ? config.content : config.content.files || []).filter((p) => typeof p === "string");
67
+ }
68
+ function extractSourcePatterns(cssContent) {
69
+ const include = [];
70
+ const exclude = [];
71
+ for (const match of cssContent.matchAll(/@source\s+(not\s+)?(?:inline\(|["']([^"']+)["'])/g)) {
72
+ const isNot = !!match[1];
73
+ const filePath = match[2];
74
+ if (!filePath) continue;
75
+ if (isNot) exclude.push(filePath);
76
+ else include.push(filePath);
77
+ }
78
+ return {
79
+ include,
80
+ exclude
81
+ };
82
+ }
83
+ async function processFiles(state, cwd, files, fix, onProgress) {
84
+ const results = [];
85
+ for (let i = 0; i < files.length; i += require_state.CONCURRENT_FILES) {
86
+ const batch = files.slice(i, i + require_state.CONCURRENT_FILES);
87
+ const batchResults = await Promise.all(batch.map(async (file, batchIndex) => {
88
+ if (onProgress) onProgress(i + batchIndex + 1, files.length, file);
89
+ return processFile(state, cwd, file, fix);
90
+ }));
91
+ results.push(...batchResults.filter((r) => r !== null));
92
+ }
93
+ return results;
94
+ }
95
+ async function processFile(state, cwd, filePath, fix) {
96
+ const absolutePath = node_path.isAbsolute(filePath) ? filePath : node_path.resolve(cwd, filePath);
97
+ if (!require_state.fileExists(absolutePath)) return null;
98
+ const content = require_state.readFileSync(absolutePath);
99
+ let diagnostics = await validateDocument(state, absolutePath, content);
100
+ let fixedCount = 0;
101
+ if (fix && diagnostics.length > 0) {
102
+ const fixResult = await require_state.applyCodeActions(state, absolutePath, content, diagnostics);
103
+ if (fixResult.changed) {
104
+ require_state.writeFileSync(absolutePath, fixResult.content);
105
+ fixedCount = fixResult.fixedCount;
106
+ diagnostics = await validateDocument(state, absolutePath, fixResult.content);
107
+ }
108
+ }
109
+ return {
110
+ path: node_path.relative(cwd, absolutePath),
111
+ diagnostics,
112
+ fixed: fixedCount > 0,
113
+ fixedCount
114
+ };
115
+ }
116
+ async function initializeState(cwd, configPath, verbose = false) {
117
+ try {
118
+ const state = await require_state.createState(cwd, configPath, verbose);
119
+ if (verbose) console.log();
120
+ return state;
121
+ } catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ throw new Error(`Failed to initialize Tailwind state: ${message}`);
124
+ }
125
+ }
126
+ async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
127
+ const state = await initializeState(cwd, configPath, verbose);
128
+ const files = await discoverFiles(cwd, patterns, configPath, autoDiscover);
129
+ if (verbose) {
130
+ console.log(chalk.default.cyan.bold(`→ Discovered ${files.length} file${files.length !== 1 ? "s" : ""} to lint`));
131
+ console.log();
132
+ }
133
+ if (files.length === 0) return {
134
+ files: [],
135
+ totalFilesProcessed: 0
136
+ };
137
+ return {
138
+ files: (await processFiles(state, cwd, files, fix, onProgress)).filter((result) => result.diagnostics.length > 0 || result.fixed),
139
+ totalFilesProcessed: files.length
140
+ };
141
+ }
142
+
143
+ //#endregion
144
+ exports.extractSourcePatterns = extractSourcePatterns;
145
+ exports.lint = lint;
package/dist/linter.d.cts CHANGED
@@ -22,6 +22,10 @@ interface LintResult {
22
22
  }
23
23
  //#endregion
24
24
  //#region src/linter.d.ts
25
+ declare function extractSourcePatterns(cssContent: string): {
26
+ include: string[];
27
+ exclude: string[];
28
+ };
25
29
  declare function lint({
26
30
  cwd,
27
31
  patterns,
@@ -32,4 +36,4 @@ declare function lint({
32
36
  onProgress
33
37
  }: LintOptions): Promise<LintResult>;
34
38
  //#endregion
35
- export { type LintFileResult, type LintOptions, type LintResult, lint };
39
+ export { type LintFileResult, type LintOptions, type LintResult, extractSourcePatterns, lint };
@@ -1,4 +1,4 @@
1
- //#region rolldown:runtime
1
+ //#region \0rolldown/runtime.js
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -30,12 +30,12 @@ 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);
35
33
  let vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
36
34
  let node_module = require("node:module");
37
35
  let node_fs = require("node:fs");
38
36
  node_fs = __toESM(node_fs);
37
+ let postcss = require("postcss");
38
+ postcss = __toESM(postcss);
39
39
 
40
40
  //#region src/constants.ts
41
41
  const DEFAULT_IGNORE_PATTERNS = [
@@ -51,7 +51,7 @@ const DEFAULT_IGNORE_PATTERNS = [
51
51
  "**/.cache/**",
52
52
  "**/.DS_Store/**"
53
53
  ];
54
- const DEFAULT_FILE_PATTERN = "./**/*.{js,jsx,ts,tsx,html}";
54
+ const DEFAULT_FILE_PATTERN = "./**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}";
55
55
  const V3_CONFIG_PATHS = [
56
56
  "tailwind.config.js",
57
57
  "tailwind.config.cjs",
@@ -66,11 +66,13 @@ const V4_CSS_NAMES = [
66
66
  "global.css",
67
67
  "styles.css",
68
68
  "style.css",
69
- "main.css"
69
+ "main.css",
70
+ "input.css"
70
71
  ];
71
72
  const V4_CSS_FOLDERS = [
72
73
  "./",
73
74
  "./src/",
75
+ "./src/app/",
74
76
  "./src/css/",
75
77
  "./src/style/",
76
78
  "./src/styles/",
@@ -78,9 +80,12 @@ const V4_CSS_FOLDERS = [
78
80
  "./app/css/",
79
81
  "./app/style/",
80
82
  "./app/styles/",
83
+ "./app/assets/css/",
81
84
  "./css/",
82
85
  "./style/",
83
- "./styles/"
86
+ "./styles/",
87
+ "./assets/css/",
88
+ "./resources/css/"
84
89
  ];
85
90
  const LANGUAGE_MAP = {
86
91
  ".astro": "astro",
@@ -116,6 +121,7 @@ const TERMINAL_WIDTH = 80;
116
121
  const TERMINAL_PADDING = 10;
117
122
  const MAX_FIX_ITERATIONS = 100;
118
123
  const QUICKFIX_ACTION_KIND = "quickfix";
124
+ const TAILWIND_V4_IMPORT_REGEX = /@import\s+["']tailwindcss(?:["'\s/]|$)/;
119
125
  function getLanguageId(filePath) {
120
126
  return LANGUAGE_MAP[filePath.substring(filePath.lastIndexOf(".")).toLowerCase()] || "html";
121
127
  }
@@ -216,6 +222,7 @@ function createEditorState(cwd) {
216
222
  const settings = {
217
223
  editor: { tabSize: DEFAULT_TAB_SIZE },
218
224
  tailwindCSS: {
225
+ validate: true,
219
226
  inspectPort: null,
220
227
  emmetCompletions: false,
221
228
  includeLanguages: {},
@@ -235,7 +242,6 @@ function createEditorState(cwd) {
235
242
  hovers: true,
236
243
  codeLens: false,
237
244
  suggestions: true,
238
- validate: true,
239
245
  colorDecorators: true,
240
246
  rootFontSize: DEFAULT_ROOT_FONT_SIZE,
241
247
  showPixelEquivalents: true,
@@ -332,6 +338,19 @@ async function loadV3ClassMetadata(state, cwd, verbose = false) {
332
338
  } };
333
339
  }
334
340
  extractConfigMetadata(state);
341
+ if (!state.classNames) state.classNames = {
342
+ context: {},
343
+ classNames: {}
344
+ };
345
+ if (state.modules?.jit?.createContext && state.config) try {
346
+ state.jitContext = state.modules.jit.createContext.module(state.config);
347
+ if (verbose) console.log(chalk.default.dim(" ✓ Created JIT context"));
348
+ } catch (contextError) {
349
+ if (verbose) {
350
+ const message = contextError instanceof Error ? contextError.message : String(contextError);
351
+ console.log(chalk.default.yellow(` ⚠ Warning: Could not create JIT context: ${message}`));
352
+ }
353
+ }
335
354
  } catch (error) {
336
355
  if (error instanceof Error) throw new AdapterLoadError("v3", error);
337
356
  throw new Error(`Failed to load v3 class metadata: ${String(error)}`);
@@ -371,6 +390,19 @@ function writeFileSync(filePath, content) {
371
390
  if (typeof content !== "string") throw new TypeError("Content must be a string");
372
391
  node_fs.writeFileSync(filePath, content, "utf-8");
373
392
  }
393
+ function readGitignorePatterns(cwd) {
394
+ const gitignorePath = node_path.join(cwd, ".gitignore");
395
+ if (!fileExists(gitignorePath)) return [];
396
+ try {
397
+ return node_fs.readFileSync(gitignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).map((pattern) => {
398
+ const cleaned = pattern.replace(/\/+$/, "");
399
+ if (cleaned.includes("/") || cleaned.includes("*")) return cleaned.endsWith("/**") ? cleaned : `${cleaned}/**`;
400
+ return `**/${cleaned}/**`;
401
+ });
402
+ } catch {
403
+ return [];
404
+ }
405
+ }
374
406
 
375
407
  //#endregion
376
408
  //#region src/adapters/v4-adapter.ts
@@ -426,11 +458,15 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
426
458
  Object.assign(designSystem, {
427
459
  dependencies: () => /* @__PURE__ */ new Set(),
428
460
  compile(classes) {
429
- return (designSystem.candidatesToAst ? designSystem.candidatesToAst(classes) : designSystem.candidatesToCss?.(classes) || []).map((result) => {
430
- if (Array.isArray(result)) return result;
431
- if (result === null) return [];
432
- return [];
461
+ if (designSystem.candidatesToCss) return designSystem.candidatesToCss(classes).map((result) => {
462
+ if (typeof result === "string" && result.length > 0) try {
463
+ return postcss.default.parse(result);
464
+ } catch {
465
+ return postcss.default.root();
466
+ }
467
+ return postcss.default.root();
433
468
  });
469
+ return classes.map(() => postcss.default.root());
434
470
  }
435
471
  });
436
472
  state.designSystem = designSystem;
@@ -478,7 +514,7 @@ async function findTailwindConfigPath(cwd, configPath) {
478
514
  const fullPath = node_path.join(cwd, p);
479
515
  if (fileExists(fullPath)) try {
480
516
  const content = readFileSync(fullPath);
481
- if (content.includes("@import \"tailwindcss\"") || content.includes("@import 'tailwindcss'")) return fullPath;
517
+ if (TAILWIND_V4_IMPORT_REGEX.test(content)) return fullPath;
482
518
  } catch {}
483
519
  }
484
520
  return null;
@@ -552,125 +588,24 @@ async function createState(cwd, configPath, verbose = false) {
552
588
  }
553
589
 
554
590
  //#endregion
555
- //#region src/linter.ts
556
- async function validateDocument(state, filePath, content) {
557
- try {
558
- if (!state) throw new Error("State is not initialized");
559
- if (state.v4 && !state.designSystem) throw new Error("Design system not initialized for Tailwind v4. This might indicate a configuration issue.");
560
- if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
561
- const languageId = getLanguageId(filePath);
562
- const uri = `file://${filePath}`;
563
- return await (0, _tailwindcss_language_service.doValidate)(state, vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content));
564
- } catch (error) {
565
- const message = error instanceof Error ? error.message : String(error);
566
- if (message.includes("Cannot read") || message.includes("undefined")) {
567
- console.warn(`Warning: Language service crashed while validating ${filePath}. Skipping this file.`);
568
- return [];
569
- }
570
- throw new Error(`Failed to validate document ${filePath}: ${message}`);
571
- }
572
- }
573
- async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
574
- if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
575
- return expandPatterns(cwd, patterns);
576
- }
577
- async function expandPatterns(cwd, patterns) {
578
- return (0, fast_glob.default)(patterns, {
579
- cwd,
580
- absolute: false,
581
- ignore: DEFAULT_IGNORE_PATTERNS
582
- });
583
- }
584
- async function discoverFilesFromConfig(cwd, configPath) {
585
- const configFilePath = await findTailwindConfigPath(cwd, configPath);
586
- if (!configFilePath) throw new Error("Could not find Tailwind config for auto-discovery.\nUse --config to specify the path, or provide file patterns directly.");
587
- if (!isCssConfigFile(configFilePath)) {
588
- const config = await loadTailwindConfig(configFilePath);
589
- if (!config || !config.content) throw new Error("Tailwind config is missing the 'content' property.\nAdd a content array to specify which files to scan:\n content: ['./src/**/*.{js,jsx,ts,tsx}']");
590
- const patterns = extractContentPatterns(config);
591
- if (patterns.length === 0) throw new Error("No content patterns found in Tailwind config.\nEnsure your config has a content array with file patterns.");
592
- return expandPatterns(cwd, patterns);
593
- }
594
- const sourcePatterns = extractSourcePatterns(readFileSync(configFilePath));
595
- if (sourcePatterns.length > 0) return expandPatterns(cwd, sourcePatterns);
596
- return expandPatterns(cwd, [DEFAULT_FILE_PATTERN]);
597
- }
598
- function extractContentPatterns(config) {
599
- if (!config.content) return [];
600
- return (Array.isArray(config.content) ? config.content : config.content.files || []).filter((p) => typeof p === "string");
601
- }
602
- function extractSourcePatterns(cssContent) {
603
- const patterns = [];
604
- for (const match of cssContent.matchAll(/@source\s+["']([^"']+)["']/g)) patterns.push(match[1]);
605
- return patterns;
606
- }
607
- async function processFiles(state, cwd, files, fix, onProgress) {
608
- const results = [];
609
- for (let i = 0; i < files.length; i += CONCURRENT_FILES) {
610
- const batch = files.slice(i, i + CONCURRENT_FILES);
611
- const batchResults = await Promise.all(batch.map(async (file, batchIndex) => {
612
- if (onProgress) onProgress(i + batchIndex + 1, files.length, file);
613
- return processFile(state, cwd, file, fix);
614
- }));
615
- results.push(...batchResults.filter((r) => r !== null));
616
- }
617
- return results;
618
- }
619
- async function processFile(state, cwd, filePath, fix) {
620
- const absolutePath = node_path.isAbsolute(filePath) ? filePath : node_path.resolve(cwd, filePath);
621
- if (!fileExists(absolutePath)) return null;
622
- const content = readFileSync(absolutePath);
623
- let diagnostics = await validateDocument(state, absolutePath, content);
624
- let fixedCount = 0;
625
- if (fix && diagnostics.length > 0) {
626
- const fixResult = await applyCodeActions(state, absolutePath, content, diagnostics);
627
- if (fixResult.changed) {
628
- writeFileSync(absolutePath, fixResult.content);
629
- fixedCount = fixResult.fixedCount;
630
- diagnostics = await validateDocument(state, absolutePath, fixResult.content);
631
- }
632
- }
633
- return {
634
- path: node_path.relative(cwd, absolutePath),
635
- diagnostics,
636
- fixed: fixedCount > 0,
637
- fixedCount
638
- };
639
- }
640
- async function initializeState(cwd, configPath, verbose = false) {
641
- try {
642
- const state = await createState(cwd, configPath, verbose);
643
- if (verbose) console.log();
644
- return state;
645
- } catch (error) {
646
- const message = error instanceof Error ? error.message : String(error);
647
- throw new Error(`Failed to initialize Tailwind state: ${message}`);
648
- }
649
- }
650
- async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
651
- const state = await initializeState(cwd, configPath, verbose);
652
- const files = await discoverFiles(cwd, patterns, configPath, autoDiscover);
653
- if (verbose) {
654
- console.log(chalk.default.cyan.bold(`→ Discovered ${files.length} file${files.length !== 1 ? "s" : ""} to lint`));
655
- console.log();
656
- }
657
- if (files.length === 0) return {
658
- files: [],
659
- totalFilesProcessed: 0
660
- };
661
- return {
662
- files: (await processFiles(state, cwd, files, fix, onProgress)).filter((result) => result.diagnostics.length > 0 || result.fixed),
663
- totalFilesProcessed: files.length
664
- };
665
- }
666
-
667
- //#endregion
591
+ Object.defineProperty(exports, 'CONCURRENT_FILES', {
592
+ enumerable: true,
593
+ get: function () {
594
+ return CONCURRENT_FILES;
595
+ }
596
+ });
668
597
  Object.defineProperty(exports, 'DEFAULT_FILE_PATTERN', {
669
598
  enumerable: true,
670
599
  get: function () {
671
600
  return DEFAULT_FILE_PATTERN;
672
601
  }
673
602
  });
603
+ Object.defineProperty(exports, 'DEFAULT_IGNORE_PATTERNS', {
604
+ enumerable: true,
605
+ get: function () {
606
+ return DEFAULT_IGNORE_PATTERNS;
607
+ }
608
+ });
674
609
  Object.defineProperty(exports, 'DEFAULT_VERSION', {
675
610
  enumerable: true,
676
611
  get: function () {
@@ -701,9 +636,63 @@ Object.defineProperty(exports, '__toESM', {
701
636
  return __toESM;
702
637
  }
703
638
  });
704
- Object.defineProperty(exports, 'lint', {
639
+ Object.defineProperty(exports, 'applyCodeActions', {
640
+ enumerable: true,
641
+ get: function () {
642
+ return applyCodeActions;
643
+ }
644
+ });
645
+ Object.defineProperty(exports, 'createState', {
646
+ enumerable: true,
647
+ get: function () {
648
+ return createState;
649
+ }
650
+ });
651
+ Object.defineProperty(exports, 'fileExists', {
652
+ enumerable: true,
653
+ get: function () {
654
+ return fileExists;
655
+ }
656
+ });
657
+ Object.defineProperty(exports, 'findTailwindConfigPath', {
658
+ enumerable: true,
659
+ get: function () {
660
+ return findTailwindConfigPath;
661
+ }
662
+ });
663
+ Object.defineProperty(exports, 'getLanguageId', {
664
+ enumerable: true,
665
+ get: function () {
666
+ return getLanguageId;
667
+ }
668
+ });
669
+ Object.defineProperty(exports, 'isCssConfigFile', {
670
+ enumerable: true,
671
+ get: function () {
672
+ return isCssConfigFile;
673
+ }
674
+ });
675
+ Object.defineProperty(exports, 'loadTailwindConfig', {
676
+ enumerable: true,
677
+ get: function () {
678
+ return loadTailwindConfig;
679
+ }
680
+ });
681
+ Object.defineProperty(exports, 'readFileSync', {
682
+ enumerable: true,
683
+ get: function () {
684
+ return readFileSync;
685
+ }
686
+ });
687
+ Object.defineProperty(exports, 'readGitignorePatterns', {
688
+ enumerable: true,
689
+ get: function () {
690
+ return readGitignorePatterns;
691
+ }
692
+ });
693
+ Object.defineProperty(exports, 'writeFileSync', {
705
694
  enumerable: true,
706
695
  get: function () {
707
- return lint;
696
+ return writeFileSync;
708
697
  }
709
698
  });
package/package.json CHANGED
@@ -1,23 +1,25 @@
1
1
  {
2
2
  "name": "tailwind-lint",
3
- "version": "0.5.1",
3
+ "version": "0.7.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
- "tailwindcss",
7
- "tailwind",
8
- "linter",
9
- "lint",
10
6
  "cli",
7
+ "code-quality",
8
+ "css",
11
9
  "diagnostics",
12
10
  "intellisense",
13
- "css",
14
- "utility-first",
15
- "code-quality"
11
+ "lint",
12
+ "linter",
13
+ "tailwind",
14
+ "tailwindcss",
15
+ "utility-first"
16
16
  ],
17
17
  "homepage": "https://github.com/ph1p/tailwind-lint#readme",
18
18
  "bugs": {
19
19
  "url": "https://github.com/ph1p/tailwind-lint/issues"
20
20
  },
21
+ "license": "MIT",
22
+ "author": "Philip Stapelfeldt <me@ph1p.dev>",
21
23
  "repository": {
22
24
  "type": "git",
23
25
  "url": "https://github.com/ph1p/tailwind-lint.git"
@@ -26,29 +28,33 @@
26
28
  "type": "github",
27
29
  "url": "https://github.com/sponsors/ph1p"
28
30
  },
29
- "license": "MIT",
30
- "author": "Philip Stapelfeldt <me@ph1p.dev>",
31
- "type": "module",
32
31
  "bin": {
33
32
  "tailwind-lint": "./dist/cli.cjs"
34
33
  },
35
- "types": "./dist/linter.d.ts",
36
34
  "files": [
37
35
  "dist",
38
36
  "README.md",
39
37
  "LICENSE"
40
38
  ],
39
+ "type": "module",
40
+ "types": "./dist/linter.d.ts",
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "registry": "https://registry.npmjs.org/"
44
+ },
41
45
  "dependencies": {
42
46
  "@tailwindcss/language-service": "^0.14.29",
43
47
  "chalk": "^5.6.2",
44
- "commander": "^14.0.2",
48
+ "commander": "^14.0.3",
45
49
  "fast-glob": "^3.3.3",
50
+ "postcss": "^8.5.6",
46
51
  "vscode-languageserver-textdocument": "^1.0.12"
47
52
  },
48
53
  "devDependencies": {
49
- "@biomejs/biome": "^2.3.12",
50
- "@types/node": "^25.0.10",
51
- "tsdown": "^0.20.1",
54
+ "@types/node": "^25.2.2",
55
+ "oxfmt": "^0.28.0",
56
+ "oxlint": "^1.43.0",
57
+ "tsdown": "^0.20.3",
52
58
  "typescript": "^5.9.3",
53
59
  "vitest": "^4.0.18"
54
60
  },
@@ -56,15 +62,13 @@
56
62
  "node": ">=22.0.0",
57
63
  "pnpm": ">=10.0.0"
58
64
  },
59
- "publishConfig": {
60
- "access": "public",
61
- "registry": "https://registry.npmjs.org/"
62
- },
63
65
  "scripts": {
64
66
  "build": "tsdown",
65
67
  "dev": "tsdown --watch",
66
- "format": "biome check --write",
67
- "lint": "biome check",
68
+ "format": "oxfmt --write .",
69
+ "format:check": "oxfmt --check .",
70
+ "lint": "oxlint .",
71
+ "lint:fix": "oxlint --fix .",
68
72
  "start": "node dist/cli.cjs",
69
73
  "test": "vitest run",
70
74
  "test:coverage": "vitest run --coverage",