tailwind-lint 0.12.0 → 0.13.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 +9 -1
- package/dist/cli.cjs +9 -10
- package/dist/cli.d.cts +1 -1
- package/dist/{linter-DQMo7IYe.cjs → linter-gjoSGNMw.cjs} +311 -201
- package/dist/linter.cjs +1 -1
- package/dist/linter.d.cts +2 -10
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -60,7 +60,15 @@ tailwind-lint --verbose
|
|
|
60
60
|
**Tailwind CSS v3:**
|
|
61
61
|
|
|
62
62
|
- Finds JavaScript config files: `tailwind.config.js`, `tailwind.config.cjs`, `tailwind.config.mjs`, `tailwind.config.ts`
|
|
63
|
-
- Uses file patterns from the `content` array in your config
|
|
63
|
+
- Uses file patterns from the `content` array in your config, resolved from the package root (or from the config file with `content.relative: true`)
|
|
64
|
+
|
|
65
|
+
**Monorepos:**
|
|
66
|
+
|
|
67
|
+
- Workspace packages from `package.json` `workspaces` or `pnpm-workspace.yaml` are detected; without a workspace definition every directory with a `package.json` is treated as a package
|
|
68
|
+
- Each package gets its own config detected; packages without one fall back to the closest parent config
|
|
69
|
+
- Each file is linted with the config of the closest package that contains it, so apps with different themes or Tailwind versions (v3 and v4 side by side) are linted correctly
|
|
70
|
+
- `tailwindcss` is resolved from the config's package first, falling back to the working directory
|
|
71
|
+
- Use `--verbose` to see which config each package uses
|
|
64
72
|
|
|
65
73
|
### Options
|
|
66
74
|
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_linter = require("./linter-
|
|
2
|
+
const require_linter = require("./linter-gjoSGNMw.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");
|
|
@@ -101,15 +101,13 @@ function createJsonErrorOutput({ error, cwd, configPath, autoDiscover, fix, patt
|
|
|
101
101
|
//#region src/cli.ts
|
|
102
102
|
const MAX_FILENAME_DISPLAY_LENGTH = 50;
|
|
103
103
|
function resolveOptions(files, options) {
|
|
104
|
-
const hasConfigFlag = !!options.config;
|
|
105
104
|
const hasAutoFlag = !!options.auto;
|
|
106
105
|
const hasFiles = files.length > 0;
|
|
107
106
|
let cwd = process.cwd();
|
|
108
|
-
|
|
107
|
+
const configPath = options.config && node_path.resolve(options.config);
|
|
109
108
|
let patterns = files;
|
|
110
109
|
let autoDiscover = hasAutoFlag;
|
|
111
|
-
if (
|
|
112
|
-
if (hasConfigFlag && options.config && !hasFiles) {
|
|
110
|
+
if (configPath && !hasFiles) {
|
|
113
111
|
const configDir = node_path.dirname(configPath);
|
|
114
112
|
cwd = require_linter.isCssConfigFile(configPath) ? require_linter.findProjectRoot(configDir) : configDir;
|
|
115
113
|
patterns = [];
|
|
@@ -158,11 +156,12 @@ async function displayResults(files, fixMode) {
|
|
|
158
156
|
}
|
|
159
157
|
}
|
|
160
158
|
console.log();
|
|
161
|
-
if (totalErrors === 0 && totalWarnings === 0)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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"));
|
|
164
|
+
} else {
|
|
166
165
|
const parts = [];
|
|
167
166
|
if (totalErrors > 0) parts.push(`${totalErrors} error${totalErrors !== 1 ? "s" : ""}`);
|
|
168
167
|
if (totalWarnings > 0) parts.push(`${totalWarnings} warning${totalWarnings !== 1 ? "s" : ""}`);
|
package/dist/cli.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {}
|
|
@@ -15,7 +15,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
19
|
value: mod,
|
|
20
20
|
enumerable: true
|
|
21
21
|
}) : target, mod));
|
|
@@ -32,6 +32,7 @@ let node_fs = require("node:fs");
|
|
|
32
32
|
node_fs = __toESM(node_fs, 1);
|
|
33
33
|
let postcss = require("postcss");
|
|
34
34
|
postcss = __toESM(postcss, 1);
|
|
35
|
+
let node_fs_promises = require("node:fs/promises");
|
|
35
36
|
//#region src/constants.ts
|
|
36
37
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
37
38
|
"**/node_modules/**",
|
|
@@ -90,7 +91,6 @@ const VITE_CONFIG_PATHS = [
|
|
|
90
91
|
"vite.config.mts",
|
|
91
92
|
"vite.config.cts"
|
|
92
93
|
];
|
|
93
|
-
const SYNTHETIC_VITE_CSS_CONFIG_NAME = ".tailwind-lint.vite.css";
|
|
94
94
|
const SYNTHETIC_VITE_CSS_CONFIG_CONTENT = "@import \"tailwindcss\";";
|
|
95
95
|
const LANGUAGE_MAP = {
|
|
96
96
|
".astro": "astro",
|
|
@@ -121,7 +121,8 @@ const SEVERITY = {
|
|
|
121
121
|
const TAILWIND_V4_IMPORT_REGEX = /@import\s+["']tailwindcss(?:["'\s/]|$)/;
|
|
122
122
|
const TAILWIND_VITE_PLUGIN_REGEX = /(?:from\s+["']@tailwindcss\/vite["']|require\(\s*["']@tailwindcss\/vite["']\s*\)|import\(\s*["']@tailwindcss\/vite["']\s*\))/;
|
|
123
123
|
function getLanguageId(filePath) {
|
|
124
|
-
|
|
124
|
+
const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
|
|
125
|
+
return LANGUAGE_MAP[ext] || "html";
|
|
125
126
|
}
|
|
126
127
|
//#endregion
|
|
127
128
|
//#region src/code-actions.ts
|
|
@@ -133,7 +134,7 @@ async function getQuickfixes(state, document, uri, diagnostics) {
|
|
|
133
134
|
code: diag.code,
|
|
134
135
|
source: diag.source || "tailwindcss"
|
|
135
136
|
}));
|
|
136
|
-
|
|
137
|
+
const params = {
|
|
137
138
|
textDocument: { uri },
|
|
138
139
|
range: {
|
|
139
140
|
start: {
|
|
@@ -146,7 +147,8 @@ async function getQuickfixes(state, document, uri, diagnostics) {
|
|
|
146
147
|
}
|
|
147
148
|
},
|
|
148
149
|
context: { diagnostics: lspDiagnostics }
|
|
149
|
-
}
|
|
150
|
+
};
|
|
151
|
+
return (await (0, _tailwindcss_language_service.doCodeActions)(state, params, document)).filter((action) => action.kind === "quickfix" || action.kind?.startsWith(`quickfix.`));
|
|
150
152
|
}
|
|
151
153
|
function applyFirstQuickfix(action, uri, document, content) {
|
|
152
154
|
if (!action.edit?.changes?.[uri]) return null;
|
|
@@ -256,7 +258,7 @@ function readVsCodeTailwindSettings(cwd) {
|
|
|
256
258
|
const files = readNestedObject(raw, "tailwindCSS.files");
|
|
257
259
|
const lint = readNestedObject(raw, "tailwindCSS.lint");
|
|
258
260
|
return {
|
|
259
|
-
includeLanguages:
|
|
261
|
+
includeLanguages: asStringRecord(raw["tailwindCSS.includeLanguages"]),
|
|
260
262
|
classAttributes: asStringArray(raw["tailwindCSS.classAttributes"]),
|
|
261
263
|
classFunctions: asStringArray(raw["tailwindCSS.classFunctions"]),
|
|
262
264
|
experimental: {
|
|
@@ -274,7 +276,7 @@ function readZedTailwindSettings(cwd) {
|
|
|
274
276
|
const normalized = asRecord(serverSettings?.tailwindCSS) ?? serverSettings;
|
|
275
277
|
if (!normalized) return {};
|
|
276
278
|
return {
|
|
277
|
-
includeLanguages:
|
|
279
|
+
includeLanguages: asStringRecord(normalized.includeLanguages),
|
|
278
280
|
classAttributes: asStringArray(normalized.classAttributes),
|
|
279
281
|
classFunctions: asStringArray(normalized.classFunctions),
|
|
280
282
|
experimental: {
|
|
@@ -302,6 +304,11 @@ function readNestedObject(input, key) {
|
|
|
302
304
|
function asRecord(value) {
|
|
303
305
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
304
306
|
}
|
|
307
|
+
function asStringRecord(value) {
|
|
308
|
+
const record = asRecord(value);
|
|
309
|
+
if (!record) return;
|
|
310
|
+
return Object.fromEntries(Object.entries(record).filter((entry) => typeof entry[1] === "string"));
|
|
311
|
+
}
|
|
305
312
|
function asStringArray(value) {
|
|
306
313
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
307
314
|
}
|
|
@@ -439,6 +446,7 @@ function createEditorState(cwd) {
|
|
|
439
446
|
invalidApply: "error",
|
|
440
447
|
invalidScreen: "error",
|
|
441
448
|
invalidVariant: "error",
|
|
449
|
+
deprecatedAtRule: "warning",
|
|
442
450
|
invalidConfigPath: "error",
|
|
443
451
|
invalidTailwindDirective: "error",
|
|
444
452
|
invalidSourceDirective: "error",
|
|
@@ -455,6 +463,7 @@ function createEditorState(cwd) {
|
|
|
455
463
|
capabilities: {
|
|
456
464
|
configuration: true,
|
|
457
465
|
diagnosticRelatedInformation: true,
|
|
466
|
+
diagnosticTagSupport: false,
|
|
458
467
|
itemDefaults: []
|
|
459
468
|
},
|
|
460
469
|
getConfiguration: async () => settings,
|
|
@@ -486,49 +495,40 @@ var AdapterLoadError = class extends Error {
|
|
|
486
495
|
//#endregion
|
|
487
496
|
//#region src/adapters/v3-adapter.ts
|
|
488
497
|
const require$4 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
489
|
-
|
|
498
|
+
function interopDefault(module) {
|
|
499
|
+
return module && typeof module === "object" && "default" in module ? module.default : module;
|
|
500
|
+
}
|
|
501
|
+
async function loadV3ClassMetadata(state, resolvePaths, verbose = false) {
|
|
490
502
|
try {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
module: tailwindcss
|
|
500
|
-
},
|
|
501
|
-
jit: {
|
|
502
|
-
generateRules: { module: generateRulesModule.generateRules || ((_set, _context) => []) },
|
|
503
|
-
createContext: { module: contextUtils.createContext },
|
|
504
|
-
expandApplyAtRules: { module: generateRulesModule.expandApplyAtRules }
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
if (verbose) console.log(ansis.default.dim(" ✓ Loaded v3 JIT modules"));
|
|
508
|
-
} catch (jitError) {
|
|
509
|
-
if (verbose) {
|
|
510
|
-
const message = jitError instanceof Error ? jitError.message : String(jitError);
|
|
511
|
-
console.log(ansis.default.yellow(` ⚠ Warning: Could not load v3 JIT modules: ${message}`));
|
|
512
|
-
}
|
|
513
|
-
state.modules = { tailwindcss: {
|
|
503
|
+
const tailwindDir = node_path.dirname(require$4.resolve("tailwindcss/package.json", { paths: resolvePaths }));
|
|
504
|
+
const load = (id) => require$4(require$4.resolve(id, { paths: [tailwindDir] }));
|
|
505
|
+
const loadLib = (name) => require$4(node_path.join(tailwindDir, "lib", "lib", name));
|
|
506
|
+
const { createContext } = loadLib("setupContextUtils");
|
|
507
|
+
const { generateRules } = loadLib("generateRules");
|
|
508
|
+
state.jit = true;
|
|
509
|
+
state.modules = {
|
|
510
|
+
tailwindcss: {
|
|
514
511
|
version: state.version || "unknown",
|
|
515
|
-
module: tailwindcss
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (verbose) {
|
|
528
|
-
const message = contextError instanceof Error ? contextError.message : String(contextError);
|
|
529
|
-
console.log(ansis.default.yellow(` ⚠ Warning: Could not create JIT context: ${message}`));
|
|
512
|
+
module: load("tailwindcss")
|
|
513
|
+
},
|
|
514
|
+
postcss: {
|
|
515
|
+
version: load("postcss/package.json").version,
|
|
516
|
+
module: load("postcss")
|
|
517
|
+
},
|
|
518
|
+
postcssSelectorParser: { module: load("postcss-selector-parser") },
|
|
519
|
+
jit: {
|
|
520
|
+
generateRules: { module: generateRules },
|
|
521
|
+
createContext: { module: createContext },
|
|
522
|
+
expandApplyAtRules: { module: interopDefault(loadLib("expandApplyAtRules")) },
|
|
523
|
+
evaluateTailwindFunctions: { module: interopDefault(loadLib("evaluateTailwindFunctions")) }
|
|
530
524
|
}
|
|
531
|
-
}
|
|
525
|
+
};
|
|
526
|
+
extractConfigMetadata(state);
|
|
527
|
+
const jitContext = createContext(state.config);
|
|
528
|
+
state.jitContext = jitContext;
|
|
529
|
+
state.classList = jitContext.getClassList?.({ includeMetadata: true });
|
|
530
|
+
state.variants = jitContext.getVariants?.() ?? [];
|
|
531
|
+
if (verbose) console.log(ansis.default.dim(" ✓ Created v3 JIT context"));
|
|
532
532
|
} catch (error) {
|
|
533
533
|
if (error instanceof Error) throw new AdapterLoadError("v3", error);
|
|
534
534
|
throw new Error(`Failed to load v3 class metadata: ${String(error)}`);
|
|
@@ -540,32 +540,50 @@ function extractConfigMetadata(state) {
|
|
|
540
540
|
const theme = config.theme;
|
|
541
541
|
state.screens = Object.keys(theme?.screens ?? {});
|
|
542
542
|
state.blocklist = config.blocklist ?? [];
|
|
543
|
-
if (config.variants && typeof config.variants === "object") state.variants = Object.keys(config.variants).map((name) => ({
|
|
544
|
-
name,
|
|
545
|
-
values: [],
|
|
546
|
-
isArbitrary: false,
|
|
547
|
-
hasDash: true,
|
|
548
|
-
selectors: () => []
|
|
549
|
-
}));
|
|
550
543
|
if (config.corePlugins) state.corePlugins = Array.isArray(config.corePlugins) ? config.corePlugins : Object.keys(config.corePlugins);
|
|
551
544
|
}
|
|
552
545
|
//#endregion
|
|
553
546
|
//#region src/adapters/v4-adapter.ts
|
|
554
547
|
const require$3 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
555
|
-
|
|
548
|
+
function fromPostcss(nodes) {
|
|
549
|
+
return nodes.map((node) => {
|
|
550
|
+
switch (node.type) {
|
|
551
|
+
case "rule": return {
|
|
552
|
+
kind: "rule",
|
|
553
|
+
selector: node.selector,
|
|
554
|
+
nodes: fromPostcss(node.nodes)
|
|
555
|
+
};
|
|
556
|
+
case "atrule": return {
|
|
557
|
+
kind: "at-rule",
|
|
558
|
+
name: node.name,
|
|
559
|
+
params: node.params,
|
|
560
|
+
nodes: node.nodes ? fromPostcss(node.nodes) : null
|
|
561
|
+
};
|
|
562
|
+
case "decl": return {
|
|
563
|
+
kind: "declaration",
|
|
564
|
+
property: node.prop,
|
|
565
|
+
value: node.value,
|
|
566
|
+
important: node.important
|
|
567
|
+
};
|
|
568
|
+
default: return {
|
|
569
|
+
kind: "comment",
|
|
570
|
+
value: node.text
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
async function loadV4DesignSystem(state, resolvePaths, target, verbose = false) {
|
|
556
576
|
try {
|
|
557
|
-
const
|
|
577
|
+
const tailwindPath = require$3.resolve("tailwindcss", { paths: resolvePaths });
|
|
578
|
+
const tailwindcss = require$3(tailwindPath);
|
|
558
579
|
if (tailwindcss !== null && tailwindcss !== void 0 && (typeof tailwindcss === "object" || typeof tailwindcss === "function") && "__unstable__loadDesignSystem" in tailwindcss && typeof tailwindcss.__unstable__loadDesignSystem === "function") {
|
|
559
|
-
|
|
560
|
-
const basePath = node_path.dirname(configPath);
|
|
561
|
-
if (fileExists(configPath)) cssContent = readFileSync(configPath);
|
|
562
|
-
else cssContent = SYNTHETIC_VITE_CSS_CONFIG_CONTENT;
|
|
580
|
+
const cssContent = target.kind === "vite" ? SYNTHETIC_VITE_CSS_CONFIG_CONTENT : readFileSync(target.path);
|
|
563
581
|
const loadDesignSystem = tailwindcss.__unstable__loadDesignSystem;
|
|
564
582
|
const designSystem = await loadDesignSystem(cssContent, {
|
|
565
|
-
base:
|
|
583
|
+
base: node_path.dirname(target.path),
|
|
566
584
|
async loadModule(id, base, _type) {
|
|
567
585
|
try {
|
|
568
|
-
const modulePath = require$3.resolve(id, { paths: [base,
|
|
586
|
+
const modulePath = require$3.resolve(id, { paths: [base, ...resolvePaths] });
|
|
569
587
|
const module = require$3(modulePath);
|
|
570
588
|
return {
|
|
571
589
|
base: node_path.dirname(modulePath),
|
|
@@ -581,7 +599,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
581
599
|
content
|
|
582
600
|
};
|
|
583
601
|
if (!_id.startsWith(".") && !_id.startsWith("/")) try {
|
|
584
|
-
const pkgJsonPath = require$3.resolve(`${_id}/package.json`, { paths: [base,
|
|
602
|
+
const pkgJsonPath = require$3.resolve(`${_id}/package.json`, { paths: [base, ...resolvePaths] });
|
|
585
603
|
const pkgDir = node_path.dirname(pkgJsonPath);
|
|
586
604
|
const cssPath = node_path.join(pkgDir, "index.css");
|
|
587
605
|
if (fileExists(cssPath)) return {
|
|
@@ -603,15 +621,16 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
603
621
|
Object.assign(designSystem, {
|
|
604
622
|
dependencies: () => /* @__PURE__ */ new Set(),
|
|
605
623
|
compile(classes) {
|
|
606
|
-
if (designSystem.
|
|
607
|
-
|
|
608
|
-
|
|
624
|
+
if (designSystem.candidatesToAst) return designSystem.candidatesToAst(classes);
|
|
625
|
+
if (designSystem.candidatesToCss) return designSystem.candidatesToCss(classes).map((css) => {
|
|
626
|
+
if (!css) return [];
|
|
627
|
+
try {
|
|
628
|
+
return fromPostcss(postcss.default.parse(css).nodes);
|
|
609
629
|
} catch {
|
|
610
|
-
return
|
|
630
|
+
return [];
|
|
611
631
|
}
|
|
612
|
-
return postcss.default.root();
|
|
613
632
|
});
|
|
614
|
-
return classes.map(() =>
|
|
633
|
+
return classes.map(() => []);
|
|
615
634
|
}
|
|
616
635
|
});
|
|
617
636
|
state.designSystem = designSystem;
|
|
@@ -629,6 +648,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
629
648
|
const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
630
649
|
const CONFIG_DISCOVERY_MAX_DEPTH = 8;
|
|
631
650
|
const PROJECT_ROOT_MARKERS = ["package.json", ".git"];
|
|
651
|
+
const VITE_CONFIG_NAME_REGEX = /^vite\.config\.[cm]?[jt]s$/;
|
|
632
652
|
const isCssConfigFile = (filePath) => filePath.endsWith(".css");
|
|
633
653
|
function findProjectRoot(startDir) {
|
|
634
654
|
let current = node_path.resolve(startDir);
|
|
@@ -639,9 +659,6 @@ function findProjectRoot(startDir) {
|
|
|
639
659
|
current = parent;
|
|
640
660
|
}
|
|
641
661
|
}
|
|
642
|
-
function syntheticViteCssConfigPath(viteConfigPath) {
|
|
643
|
-
return node_path.join(node_path.dirname(viteConfigPath), SYNTHETIC_VITE_CSS_CONFIG_NAME);
|
|
644
|
-
}
|
|
645
662
|
async function loadTailwindConfig(configPath) {
|
|
646
663
|
if (isCssConfigFile(configPath)) return {};
|
|
647
664
|
if (!node_path.isAbsolute(configPath)) throw new Error(`Config path must be absolute for security reasons: ${configPath}`);
|
|
@@ -658,66 +675,116 @@ async function loadTailwindConfig(configPath) {
|
|
|
658
675
|
throw new Error(`Failed to load config from ${configPath}: ${errorMessage}`);
|
|
659
676
|
}
|
|
660
677
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
cwd,
|
|
678
|
+
function toConfigTarget(configPath) {
|
|
679
|
+
return {
|
|
680
|
+
kind: configKind(configPath),
|
|
681
|
+
path: configPath,
|
|
682
|
+
root: findProjectRoot(node_path.dirname(configPath))
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function configKind(configPath) {
|
|
686
|
+
if (isCssConfigFile(configPath)) return "css";
|
|
687
|
+
return VITE_CONFIG_NAME_REGEX.test(node_path.basename(configPath)) ? "vite" : "js";
|
|
688
|
+
}
|
|
689
|
+
async function findTailwindConfigs(cwd) {
|
|
690
|
+
const root = node_path.resolve(cwd);
|
|
691
|
+
const files = await (0, tinyglobby.glob)([
|
|
692
|
+
"**/package.json",
|
|
693
|
+
"**/*.css",
|
|
694
|
+
...[...V3_CONFIG_PATHS, ...VITE_CONFIG_PATHS].map((p) => `**/${p}`)
|
|
695
|
+
], {
|
|
696
|
+
cwd: root,
|
|
680
697
|
absolute: true,
|
|
681
698
|
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
682
699
|
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
683
700
|
});
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
701
|
+
const isPackageJson = (file) => node_path.basename(file) === "package.json";
|
|
702
|
+
const packageRoots = await findPackageRoots(root, files.filter(isPackageJson).map(node_path.dirname));
|
|
703
|
+
const deepestFirst = [...packageRoots].sort((a, b) => b.length - a.length);
|
|
704
|
+
const candidatesByRoot = new Map(packageRoots.map((packageRoot) => [packageRoot, []]));
|
|
705
|
+
for (const file of files) {
|
|
706
|
+
if (isPackageJson(file)) continue;
|
|
707
|
+
const owner = deepestFirst.find((packageRoot) => isInsideDir(packageRoot, file));
|
|
708
|
+
if (owner) candidatesByRoot.get(owner)?.push(file);
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
targets: (await Promise.all([...candidatesByRoot].map(([packageRoot, candidates]) => pickConfig(packageRoot, candidates)))).filter((target) => target !== void 0),
|
|
712
|
+
packageRoots
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
async function findPackageRoots(root, packageDirs) {
|
|
716
|
+
const workspaces = await readWorkspacePatterns(root);
|
|
717
|
+
if (!workspaces) return [.../* @__PURE__ */ new Set([root, ...packageDirs])];
|
|
718
|
+
const toPackageJson = (pattern) => `${pattern.replace(/\/+$/, "")}/package.json`;
|
|
719
|
+
const workspacePackageJsons = await (0, tinyglobby.glob)(workspaces.filter((p) => !p.startsWith("!")).map(toPackageJson), {
|
|
720
|
+
cwd: root,
|
|
692
721
|
absolute: true,
|
|
693
|
-
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
694
|
-
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
722
|
+
ignore: [...DEFAULT_IGNORE_PATTERNS, ...workspaces.filter((p) => p.startsWith("!")).map((p) => toPackageJson(p.slice(1)))]
|
|
695
723
|
});
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
724
|
+
return [.../* @__PURE__ */ new Set([root, ...workspacePackageJsons.map(node_path.dirname)])];
|
|
725
|
+
}
|
|
726
|
+
async function readWorkspacePatterns(root) {
|
|
727
|
+
const [packageJson, pnpmWorkspace] = await Promise.all([readText(node_path.join(root, "package.json")), readText(node_path.join(root, "pnpm-workspace.yaml"))]);
|
|
728
|
+
const patterns = [...parsePackageJsonWorkspaces(packageJson), ...parsePnpmWorkspacePackages(pnpmWorkspace)];
|
|
729
|
+
return patterns.length > 0 ? patterns : void 0;
|
|
730
|
+
}
|
|
731
|
+
function parsePackageJsonWorkspaces(content) {
|
|
732
|
+
try {
|
|
733
|
+
const { workspaces } = JSON.parse(content);
|
|
734
|
+
return ((Array.isArray(workspaces) ? workspaces : workspaces?.packages) ?? []).filter((p) => typeof p === "string");
|
|
735
|
+
} catch {
|
|
736
|
+
return [];
|
|
705
737
|
}
|
|
706
|
-
const matches = (await (0, tinyglobby.glob)(VITE_CONFIG_PATHS.map((p) => `**/${p}`), {
|
|
707
|
-
cwd,
|
|
708
|
-
absolute: true,
|
|
709
|
-
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
710
|
-
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
711
|
-
})).filter(isTailwindViteConfig);
|
|
712
|
-
if (matches.length === 0) return null;
|
|
713
|
-
return sortByPathDepth(matches)[0];
|
|
714
738
|
}
|
|
715
|
-
function
|
|
716
|
-
|
|
739
|
+
function parsePnpmWorkspacePackages(content) {
|
|
740
|
+
const patterns = [];
|
|
741
|
+
let inPackages = false;
|
|
742
|
+
for (const line of content.split(/\r?\n/)) if (/^packages\s*:/.test(line)) inPackages = true;
|
|
743
|
+
else if (/^[^\s#]/.test(line)) inPackages = false;
|
|
744
|
+
else if (inPackages) {
|
|
745
|
+
const item = line.match(/^\s*-\s*["']?([^"'#\s]+)["']?/);
|
|
746
|
+
if (item) patterns.push(item[1]);
|
|
747
|
+
}
|
|
748
|
+
return patterns;
|
|
749
|
+
}
|
|
750
|
+
function nestedRootIgnores(root, roots) {
|
|
751
|
+
return roots.filter((other) => other !== root && isInsideDir(root, other)).map((other) => `${node_path.relative(root, other)}/**`);
|
|
752
|
+
}
|
|
753
|
+
function isInsideDir(dir, filePath) {
|
|
754
|
+
const relative = node_path.relative(dir, filePath);
|
|
755
|
+
return !relative.startsWith("..") && !node_path.isAbsolute(relative);
|
|
756
|
+
}
|
|
757
|
+
async function pickConfig(root, candidates) {
|
|
758
|
+
const target = (kind, configPath) => ({
|
|
759
|
+
kind,
|
|
760
|
+
path: configPath,
|
|
761
|
+
root
|
|
762
|
+
});
|
|
763
|
+
const candidateSet = new Set(candidates);
|
|
764
|
+
const existing = (paths) => paths.map((p) => node_path.join(root, p)).filter((p) => candidateSet.has(p));
|
|
765
|
+
const byName = (names) => candidates.filter((c) => names.includes(node_path.basename(c)));
|
|
766
|
+
const preferredCss = existing(V4_CSS_FOLDERS.flatMap((folder) => V4_CSS_NAMES.map((name) => node_path.join(folder, name))));
|
|
767
|
+
const preferredCssMatch = await findFirstMatch(preferredCss, TAILWIND_V4_IMPORT_REGEX);
|
|
768
|
+
if (preferredCssMatch) return target("css", preferredCssMatch);
|
|
769
|
+
const rootV3 = existing(V3_CONFIG_PATHS)[0];
|
|
770
|
+
if (rootV3) return target("js", rootV3);
|
|
771
|
+
const cssMatch = await findFirstMatch(sortCssCandidates(root, candidates.filter((c) => isCssConfigFile(c) && !preferredCss.includes(c))), TAILWIND_V4_IMPORT_REGEX);
|
|
772
|
+
if (cssMatch) return target("css", cssMatch);
|
|
773
|
+
const nestedV3 = sortByPathDepth(byName(V3_CONFIG_PATHS))[0];
|
|
774
|
+
if (nestedV3) return target("js", nestedV3);
|
|
775
|
+
const viteConfigs = [...existing(VITE_CONFIG_PATHS), ...sortByPathDepth(byName(VITE_CONFIG_PATHS))];
|
|
776
|
+
const viteMatch = await findFirstMatch([...new Set(viteConfigs)], TAILWIND_VITE_PLUGIN_REGEX);
|
|
777
|
+
if (viteMatch) return target("vite", viteMatch);
|
|
778
|
+
}
|
|
779
|
+
async function findFirstMatch(paths, regex) {
|
|
780
|
+
const contents = await Promise.all(paths.map(readText));
|
|
781
|
+
return paths.find((_, index) => regex.test(contents[index]));
|
|
782
|
+
}
|
|
783
|
+
async function readText(filePath) {
|
|
717
784
|
try {
|
|
718
|
-
return
|
|
785
|
+
return await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
719
786
|
} catch {
|
|
720
|
-
return
|
|
787
|
+
return "";
|
|
721
788
|
}
|
|
722
789
|
}
|
|
723
790
|
function sortByPathDepth(paths) {
|
|
@@ -755,9 +822,9 @@ function splitDepth(value) {
|
|
|
755
822
|
//#endregion
|
|
756
823
|
//#region src/state.ts
|
|
757
824
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
758
|
-
function getTailwindVersion(
|
|
825
|
+
function getTailwindVersion(paths) {
|
|
759
826
|
try {
|
|
760
|
-
const tailwindPackageJson = require$1.resolve("tailwindcss/package.json", { paths
|
|
827
|
+
const tailwindPackageJson = require$1.resolve("tailwindcss/package.json", { paths });
|
|
761
828
|
const { readFileSync } = require$1("node:fs");
|
|
762
829
|
return JSON.parse(readFileSync(tailwindPackageJson, "utf-8")).version;
|
|
763
830
|
} catch {
|
|
@@ -767,40 +834,52 @@ function getTailwindVersion(cwd) {
|
|
|
767
834
|
function isV4Config(version) {
|
|
768
835
|
return version?.startsWith("4.") ?? false;
|
|
769
836
|
}
|
|
770
|
-
function resolveTailwindPath(
|
|
771
|
-
const paths = configDir ? [configDir, cwd] : [cwd];
|
|
837
|
+
function resolveTailwindPath(paths) {
|
|
772
838
|
try {
|
|
773
839
|
return require$1.resolve("tailwindcss", { paths });
|
|
774
840
|
} catch {
|
|
775
841
|
throw new Error(`Could not find tailwindcss module in ${paths.join(" or ")}.\nInstall it with: npm install -D tailwindcss`);
|
|
776
842
|
}
|
|
777
843
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
844
|
+
function loadResolveConfig(paths) {
|
|
845
|
+
try {
|
|
846
|
+
const module = require$1(require$1.resolve("tailwindcss/resolveConfig", { paths }));
|
|
847
|
+
return typeof module === "function" ? module : module.default;
|
|
848
|
+
} catch {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const CONFIG_TYPE_LABELS = {
|
|
853
|
+
css: "CSS (v4)",
|
|
854
|
+
vite: "Vite (v4)",
|
|
855
|
+
js: "JavaScript"
|
|
856
|
+
};
|
|
857
|
+
async function createState(cwd, target, verbose = false) {
|
|
858
|
+
const isCssConfig = target.kind !== "js";
|
|
859
|
+
const resolvePaths = [node_path.dirname(target.path), cwd];
|
|
860
|
+
resolveTailwindPath(resolvePaths);
|
|
861
|
+
const version = getTailwindVersion(resolvePaths);
|
|
784
862
|
const isV4 = isV4Config(version);
|
|
785
863
|
if (verbose) {
|
|
786
864
|
console.log(ansis.default.cyan.bold("→ Tailwind Configuration"));
|
|
787
865
|
console.log(ansis.default.dim(` Version: ${version || "unknown"}`));
|
|
788
|
-
console.log(ansis.default.dim(` Config type: ${
|
|
789
|
-
console.log(ansis.default.dim(` Config path: ${
|
|
866
|
+
console.log(ansis.default.dim(` Config type: ${CONFIG_TYPE_LABELS[target.kind]}`));
|
|
867
|
+
console.log(ansis.default.dim(` Config path: ${target.path}`));
|
|
790
868
|
}
|
|
791
869
|
let config = {};
|
|
792
870
|
let resolvedConfig = { separator: ":" };
|
|
793
871
|
if (!isCssConfig) {
|
|
794
|
-
config = await loadTailwindConfig(
|
|
872
|
+
config = await loadTailwindConfig(target.path);
|
|
795
873
|
resolvedConfig = {
|
|
796
874
|
...config,
|
|
797
875
|
separator: config.separator ?? ":"
|
|
798
876
|
};
|
|
799
|
-
|
|
877
|
+
const resolveConfig = loadResolveConfig(resolvePaths);
|
|
878
|
+
if (resolveConfig) resolvedConfig = resolveConfig(config);
|
|
800
879
|
}
|
|
801
880
|
const state = {
|
|
802
881
|
enabled: true,
|
|
803
|
-
configPath:
|
|
882
|
+
configPath: target.path,
|
|
804
883
|
config: resolvedConfig,
|
|
805
884
|
version,
|
|
806
885
|
v4: isV4 || void 0,
|
|
@@ -814,14 +893,15 @@ async function createState(cwd, configPath, verbose = false) {
|
|
|
814
893
|
editor: createEditorState(cwd),
|
|
815
894
|
features: ["diagnostics"]
|
|
816
895
|
};
|
|
817
|
-
if (isV4 || isCssConfig) await loadV4DesignSystem(state,
|
|
818
|
-
else await loadV3ClassMetadata(state,
|
|
896
|
+
if (isV4 || isCssConfig) await loadV4DesignSystem(state, resolvePaths, target, verbose);
|
|
897
|
+
else await loadV3ClassMetadata(state, resolvePaths, verbose);
|
|
819
898
|
return state;
|
|
820
899
|
}
|
|
821
900
|
//#endregion
|
|
822
901
|
//#region src/linter.ts
|
|
823
902
|
const SOURCE_FILE_PATTERN = "**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}";
|
|
824
903
|
const GLOB_PATTERN_REGEX = /[*?[\]{}]/;
|
|
904
|
+
const MAX_LISTED_PACKAGES = 10;
|
|
825
905
|
async function validateDocument(state, filePath, content) {
|
|
826
906
|
try {
|
|
827
907
|
if (!state) throw new Error("State is not initialized");
|
|
@@ -829,7 +909,8 @@ async function validateDocument(state, filePath, content) {
|
|
|
829
909
|
if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
|
|
830
910
|
const languageId = getLanguageId(filePath);
|
|
831
911
|
const uri = `file://${filePath}`;
|
|
832
|
-
|
|
912
|
+
const document = vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content);
|
|
913
|
+
return await (0, _tailwindcss_language_service.doValidate)(state, document);
|
|
833
914
|
} catch (error) {
|
|
834
915
|
const message = error instanceof Error ? error.message : String(error);
|
|
835
916
|
if (message.includes("Cannot read") || message.includes("undefined")) {
|
|
@@ -840,52 +921,43 @@ async function validateDocument(state, filePath, content) {
|
|
|
840
921
|
throw new Error(`Failed to validate document ${filePath}: ${message}`);
|
|
841
922
|
}
|
|
842
923
|
}
|
|
843
|
-
async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
|
|
844
|
-
if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
|
|
845
|
-
return expandPatterns(cwd, patterns);
|
|
846
|
-
}
|
|
847
924
|
async function expandPatterns(cwd, patterns, extraIgnore = []) {
|
|
848
|
-
|
|
925
|
+
return (0, tinyglobby.glob)(patterns, {
|
|
849
926
|
cwd,
|
|
850
|
-
absolute:
|
|
927
|
+
absolute: true,
|
|
851
928
|
ignore: [...DEFAULT_IGNORE_PATTERNS, ...extraIgnore]
|
|
852
929
|
});
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
if (!configFilePath) throw new Error("Could not find Tailwind config for auto-discovery.\nUse --config to specify the path, or provide file patterns directly.");
|
|
858
|
-
const discoveryCwd = isCssConfigFile(configFilePath) ? findProjectRoot(node_path.dirname(configFilePath)) : cwd;
|
|
859
|
-
if (!isCssConfigFile(configFilePath)) {
|
|
860
|
-
const config = await loadTailwindConfig(configFilePath);
|
|
930
|
+
}
|
|
931
|
+
async function discoverFilesFromConfig(target, configRoots) {
|
|
932
|
+
if (target.kind === "js") {
|
|
933
|
+
const config = await loadTailwindConfig(target.path);
|
|
861
934
|
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}']");
|
|
862
935
|
const patterns = extractContentPatterns(config);
|
|
863
936
|
if (patterns.length === 0) throw new Error("No content patterns found in Tailwind config.\nEnsure your config has a content array with file patterns.");
|
|
864
|
-
return expandPatterns(
|
|
937
|
+
return expandPatterns(!Array.isArray(config.content) && config.content.relative === true ? node_path.dirname(target.path) : target.root, patterns);
|
|
865
938
|
}
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
if (!fileExists(configFilePath)) return files;
|
|
870
|
-
const relativeConfigPath = node_path.relative(discoveryCwd, configFilePath);
|
|
871
|
-
return [...new Set([relativeConfigPath, ...files])].sort((a, b) => a.localeCompare(b));
|
|
872
|
-
};
|
|
873
|
-
const resolveDiscoveredFiles = (files) => discoveryCwd === cwd ? files : files.map((file) => node_path.resolve(discoveryCwd, file)).sort((a, b) => a.localeCompare(b));
|
|
939
|
+
const { root } = target;
|
|
940
|
+
const configDir = node_path.dirname(target.path);
|
|
941
|
+
const cssContent = target.kind === "vite" ? SYNTHETIC_VITE_CSS_CONFIG_CONTENT : readFileSync(target.path);
|
|
874
942
|
const { include, exclude } = extractSourcePatterns(cssContent);
|
|
875
943
|
const importSource = extractImportSourceDirectives(cssContent);
|
|
876
|
-
const resolveFromConfig = (pattern) =>
|
|
877
|
-
const absolutePattern = node_path.resolve(configDir, pattern);
|
|
878
|
-
return node_path.relative(discoveryCwd, absolutePattern);
|
|
879
|
-
};
|
|
944
|
+
const resolveFromConfig = (pattern) => node_path.relative(root, node_path.resolve(configDir, pattern));
|
|
880
945
|
const resolvedExclude = exclude.map(resolveFromConfig);
|
|
881
|
-
const
|
|
882
|
-
|
|
946
|
+
const extraIgnore = [
|
|
947
|
+
...resolvedExclude,
|
|
948
|
+
...readGitignorePatterns(root),
|
|
949
|
+
...nestedRootIgnores(root, configRoots)
|
|
950
|
+
];
|
|
883
951
|
const explicitSourcePatterns = include.map((source) => resolveFromConfig(normalizeSourcePattern(source)));
|
|
884
952
|
let autoPatterns = [];
|
|
885
|
-
if (!importSource.disableAutoSource) autoPatterns = importSource.roots.length > 0 ? importSource.roots.map((
|
|
886
|
-
const autoFiles = autoPatterns.length > 0 ?
|
|
887
|
-
const
|
|
888
|
-
return
|
|
953
|
+
if (!importSource.disableAutoSource) autoPatterns = importSource.roots.length > 0 ? importSource.roots.map((sourceRoot) => resolveFromConfig(normalizeSourcePattern(sourceRoot))) : [DEFAULT_FILE_PATTERN];
|
|
954
|
+
const [autoFiles, explicitFiles] = await Promise.all([autoPatterns.length > 0 ? expandPatterns(root, autoPatterns, extraIgnore) : [], explicitSourcePatterns.length > 0 ? expandPatterns(root, explicitSourcePatterns, resolvedExclude) : []]);
|
|
955
|
+
const configFile = target.kind === "css" ? [target.path] : [];
|
|
956
|
+
return [.../* @__PURE__ */ new Set([
|
|
957
|
+
...configFile,
|
|
958
|
+
...autoFiles,
|
|
959
|
+
...explicitFiles
|
|
960
|
+
])];
|
|
889
961
|
}
|
|
890
962
|
function normalizeSourcePattern(pattern) {
|
|
891
963
|
if (GLOB_PATTERN_REGEX.test(pattern) || node_path.extname(pattern)) return pattern;
|
|
@@ -961,9 +1033,9 @@ async function processFile(state, cwd, filePath, fix) {
|
|
|
961
1033
|
fixedCount
|
|
962
1034
|
};
|
|
963
1035
|
}
|
|
964
|
-
async function initializeState(cwd,
|
|
1036
|
+
async function initializeState(cwd, target, verbose = false) {
|
|
965
1037
|
try {
|
|
966
|
-
const state = await createState(cwd,
|
|
1038
|
+
const state = await createState(cwd, target, verbose);
|
|
967
1039
|
if (verbose) console.log();
|
|
968
1040
|
return state;
|
|
969
1041
|
} catch (error) {
|
|
@@ -972,42 +1044,80 @@ async function initializeState(cwd, configPath, verbose = false) {
|
|
|
972
1044
|
}
|
|
973
1045
|
}
|
|
974
1046
|
async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
const
|
|
1047
|
+
const targets = await resolveConfigTargets(cwd, configPath);
|
|
1048
|
+
if (verbose) logTargets(cwd, targets);
|
|
1049
|
+
const configRoots = targets.map((target) => target.root);
|
|
1050
|
+
const patternFiles = autoDiscover ? [] : await expandPatterns(cwd, patterns);
|
|
1051
|
+
const discoveredFilesByTarget = await Promise.all(targets.map((target) => autoDiscover ? discoverFilesFromConfig(target, configRoots) : patternFiles));
|
|
1052
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1053
|
+
for (const [index, target] of targets.entries()) {
|
|
1054
|
+
const files = target.kind === "css" && !autoDiscover ? [target.path, ...discoveredFilesByTarget[index]] : discoveredFilesByTarget[index];
|
|
1055
|
+
for (const file of files) {
|
|
1056
|
+
const current = owners.get(file);
|
|
1057
|
+
if (!current || ownershipScore(target, file) > ownershipScore(current, file)) owners.set(file, target);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
978
1060
|
const skippedFiles = [];
|
|
979
|
-
const
|
|
980
|
-
|
|
1061
|
+
const filesByTarget = /* @__PURE__ */ new Map();
|
|
1062
|
+
for (const [absolutePath, target] of owners) {
|
|
1063
|
+
const file = node_path.relative(cwd, absolutePath);
|
|
1064
|
+
if (getFileSize(absolutePath) > 524288) {
|
|
981
1065
|
skippedFiles.push(file);
|
|
982
|
-
|
|
1066
|
+
continue;
|
|
983
1067
|
}
|
|
984
|
-
|
|
985
|
-
|
|
1068
|
+
const files = filesByTarget.get(target) ?? [];
|
|
1069
|
+
files.push(file);
|
|
1070
|
+
filesByTarget.set(target, files);
|
|
1071
|
+
}
|
|
1072
|
+
const totalFiles = owners.size - skippedFiles.length;
|
|
986
1073
|
if (verbose) {
|
|
987
|
-
console.log(ansis.default.cyan.bold(`→ Discovered ${
|
|
1074
|
+
console.log(ansis.default.cyan.bold(`→ Discovered ${totalFiles} file${totalFiles !== 1 ? "s" : ""} to lint`));
|
|
988
1075
|
console.log();
|
|
989
1076
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1077
|
+
const results = [];
|
|
1078
|
+
let processedCount = 0;
|
|
1079
|
+
for (const target of targets) {
|
|
1080
|
+
const files = filesByTarget.get(target);
|
|
1081
|
+
if (!files) continue;
|
|
1082
|
+
const offset = processedCount;
|
|
1083
|
+
processedCount += files.length;
|
|
1084
|
+
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)));
|
|
1085
|
+
results.push(...processed);
|
|
1086
|
+
}
|
|
995
1087
|
return {
|
|
996
|
-
files:
|
|
997
|
-
totalFilesProcessed:
|
|
998
|
-
skippedFiles
|
|
1088
|
+
files: results.filter((result) => result.diagnostics.length > 0 || result.fixed).sort((a, b) => a.path.localeCompare(b.path)),
|
|
1089
|
+
totalFilesProcessed: totalFiles,
|
|
1090
|
+
skippedFiles: skippedFiles.sort((a, b) => a.localeCompare(b))
|
|
999
1091
|
};
|
|
1000
1092
|
}
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
if (seen.has(absolutePath)) continue;
|
|
1007
|
-
seen.add(absolutePath);
|
|
1008
|
-
unique.push(file);
|
|
1093
|
+
async function resolveConfigTargets(cwd, configPath) {
|
|
1094
|
+
if (configPath) {
|
|
1095
|
+
const absolutePath = node_path.resolve(cwd, configPath);
|
|
1096
|
+
if (!fileExists(absolutePath)) throw new Error(`Tailwind config file not found: ${absolutePath}`);
|
|
1097
|
+
return [toConfigTarget(absolutePath)];
|
|
1009
1098
|
}
|
|
1010
|
-
|
|
1099
|
+
const { targets, packageRoots } = await findTailwindConfigs(cwd);
|
|
1100
|
+
if (targets.length === 0) throw new Error(noConfigMessage(cwd, packageRoots));
|
|
1101
|
+
return targets;
|
|
1102
|
+
}
|
|
1103
|
+
function noConfigMessage(cwd, packageRoots) {
|
|
1104
|
+
const packages = packageRoots.map((root) => node_path.relative(cwd, root)).filter(Boolean).sort((a, b) => a.localeCompare(b));
|
|
1105
|
+
const shown = packages.slice(0, MAX_LISTED_PACKAGES);
|
|
1106
|
+
const more = packages.length - shown.length;
|
|
1107
|
+
return `Could not find a Tailwind config in ${cwd}${packages.length > 0 ? ` or its packages:\n${shown.map((p) => ` • ${p}`).join("\n")}${more > 0 ? `\n …and ${more} more` : ""}` : ""}\nExpected one of:
|
|
1108
|
+
• Tailwind v4 (CSS): a CSS file with @import "tailwindcss", e.g. app.css or src/index.css
|
|
1109
|
+
• Tailwind v4 (Vite): vite.config.* with @tailwindcss/vite
|
|
1110
|
+
• Tailwind v3 (JS): tailwind.config.js, tailwind.config.ts
|
|
1111
|
+
Use --config to specify the path.`;
|
|
1112
|
+
}
|
|
1113
|
+
function logTargets(cwd, targets) {
|
|
1114
|
+
const display = (p) => node_path.relative(cwd, p) || ".";
|
|
1115
|
+
console.log(ansis.default.cyan.bold("→ Tailwind configs"));
|
|
1116
|
+
for (const target of targets) console.log(ansis.default.dim(` ${display(target.root)} → ${display(target.path)}`));
|
|
1117
|
+
console.log();
|
|
1118
|
+
}
|
|
1119
|
+
function ownershipScore(target, absolutePath) {
|
|
1120
|
+
return isInsideDir(target.root, absolutePath) ? target.root.length : -1;
|
|
1011
1121
|
}
|
|
1012
1122
|
//#endregion
|
|
1013
1123
|
Object.defineProperty(exports, "DEFAULT_FILE_PATTERN", {
|
package/dist/linter.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_linter = require("./linter-
|
|
2
|
+
const require_linter = require("./linter-gjoSGNMw.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
|
@@ -383,14 +383,6 @@ declare function extractImportSourceDirectives(cssContent: string): {
|
|
|
383
383
|
roots: string[];
|
|
384
384
|
disableAutoSource: boolean;
|
|
385
385
|
};
|
|
386
|
-
declare function lint({
|
|
387
|
-
cwd,
|
|
388
|
-
patterns,
|
|
389
|
-
configPath,
|
|
390
|
-
autoDiscover,
|
|
391
|
-
fix,
|
|
392
|
-
verbose,
|
|
393
|
-
onProgress
|
|
394
|
-
}: LintOptions): Promise<LintResult>;
|
|
386
|
+
export declare function lint({ cwd, patterns, configPath, autoDiscover, fix, verbose, onProgress }: LintOptions): Promise<LintResult>;
|
|
395
387
|
//#endregion
|
|
396
|
-
export { type LintFileResult, type LintOptions, type LintResult, extractImportSourceDirectives, extractSourcePatterns
|
|
388
|
+
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.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -58,24 +58,25 @@
|
|
|
58
58
|
"test:watch": "vitest"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@tailwindcss/language-service": "^0.
|
|
62
|
-
"ansis": "^4.
|
|
61
|
+
"@tailwindcss/language-service": "^0.16.0",
|
|
62
|
+
"ansis": "^4.4.0",
|
|
63
63
|
"commander": "^15.0.0",
|
|
64
|
-
"postcss": "^8.5.
|
|
64
|
+
"postcss": "^8.5.28",
|
|
65
65
|
"tinyglobby": "^0.2.17",
|
|
66
|
-
"vscode-languageserver-textdocument": "^1.0.
|
|
66
|
+
"vscode-languageserver-textdocument": "^1.0.15"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@types/node": "^
|
|
70
|
-
"oxfmt": "^0.
|
|
71
|
-
"oxlint": "^1.
|
|
72
|
-
"tsdown": "^0.
|
|
73
|
-
"typescript": "^
|
|
74
|
-
"vitest": "^
|
|
69
|
+
"@types/node": "^26.6.4",
|
|
70
|
+
"oxfmt": "^0.71.0",
|
|
71
|
+
"oxlint": "^1.86.0",
|
|
72
|
+
"tsdown": "^0.23.0",
|
|
73
|
+
"typescript": "^7.0.2",
|
|
74
|
+
"vitest": "^5.0.3",
|
|
75
|
+
"vscode-languageserver": "8.1.0"
|
|
75
76
|
},
|
|
76
77
|
"engines": {
|
|
77
78
|
"node": ">=22.0.0",
|
|
78
79
|
"pnpm": ">=10.0.0"
|
|
79
80
|
},
|
|
80
|
-
"packageManager": "pnpm@
|
|
81
|
+
"packageManager": "pnpm@12.8.1"
|
|
81
82
|
}
|