tailwind-lint 0.12.1 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/cli.cjs +9 -10
- package/dist/cli.d.cts +1 -1
- package/dist/{linter-BP0uKBfa.cjs → linter-B-Bi_uRJ.cjs} +329 -204
- package/dist/linter.cjs +1 -1
- package/dist/linter.d.cts +2 -2
- package/package.json +12 -11
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-B-Bi_uRJ.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));
|
|
@@ -30,8 +30,10 @@ let vscode_languageserver_textdocument = require("vscode-languageserver-textdocu
|
|
|
30
30
|
let node_module = require("node:module");
|
|
31
31
|
let node_fs = require("node:fs");
|
|
32
32
|
node_fs = __toESM(node_fs, 1);
|
|
33
|
+
let node_url = require("node:url");
|
|
33
34
|
let postcss = require("postcss");
|
|
34
35
|
postcss = __toESM(postcss, 1);
|
|
36
|
+
let node_fs_promises = require("node:fs/promises");
|
|
35
37
|
//#region src/constants.ts
|
|
36
38
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
37
39
|
"**/node_modules/**",
|
|
@@ -90,7 +92,6 @@ const VITE_CONFIG_PATHS = [
|
|
|
90
92
|
"vite.config.mts",
|
|
91
93
|
"vite.config.cts"
|
|
92
94
|
];
|
|
93
|
-
const SYNTHETIC_VITE_CSS_CONFIG_NAME = ".tailwind-lint.vite.css";
|
|
94
95
|
const SYNTHETIC_VITE_CSS_CONFIG_CONTENT = "@import \"tailwindcss\";";
|
|
95
96
|
const LANGUAGE_MAP = {
|
|
96
97
|
".astro": "astro",
|
|
@@ -134,7 +135,7 @@ async function getQuickfixes(state, document, uri, diagnostics) {
|
|
|
134
135
|
code: diag.code,
|
|
135
136
|
source: diag.source || "tailwindcss"
|
|
136
137
|
}));
|
|
137
|
-
|
|
138
|
+
const params = {
|
|
138
139
|
textDocument: { uri },
|
|
139
140
|
range: {
|
|
140
141
|
start: {
|
|
@@ -147,7 +148,8 @@ async function getQuickfixes(state, document, uri, diagnostics) {
|
|
|
147
148
|
}
|
|
148
149
|
},
|
|
149
150
|
context: { diagnostics: lspDiagnostics }
|
|
150
|
-
}
|
|
151
|
+
};
|
|
152
|
+
return (await (0, _tailwindcss_language_service.doCodeActions)(state, params, document)).filter((action) => action.kind === "quickfix" || action.kind?.startsWith(`quickfix.`));
|
|
151
153
|
}
|
|
152
154
|
function applyFirstQuickfix(action, uri, document, content) {
|
|
153
155
|
if (!action.edit?.changes?.[uri]) return null;
|
|
@@ -257,7 +259,7 @@ function readVsCodeTailwindSettings(cwd) {
|
|
|
257
259
|
const files = readNestedObject(raw, "tailwindCSS.files");
|
|
258
260
|
const lint = readNestedObject(raw, "tailwindCSS.lint");
|
|
259
261
|
return {
|
|
260
|
-
includeLanguages:
|
|
262
|
+
includeLanguages: asStringRecord(raw["tailwindCSS.includeLanguages"]),
|
|
261
263
|
classAttributes: asStringArray(raw["tailwindCSS.classAttributes"]),
|
|
262
264
|
classFunctions: asStringArray(raw["tailwindCSS.classFunctions"]),
|
|
263
265
|
experimental: {
|
|
@@ -275,7 +277,7 @@ function readZedTailwindSettings(cwd) {
|
|
|
275
277
|
const normalized = asRecord(serverSettings?.tailwindCSS) ?? serverSettings;
|
|
276
278
|
if (!normalized) return {};
|
|
277
279
|
return {
|
|
278
|
-
includeLanguages:
|
|
280
|
+
includeLanguages: asStringRecord(normalized.includeLanguages),
|
|
279
281
|
classAttributes: asStringArray(normalized.classAttributes),
|
|
280
282
|
classFunctions: asStringArray(normalized.classFunctions),
|
|
281
283
|
experimental: {
|
|
@@ -303,6 +305,11 @@ function readNestedObject(input, key) {
|
|
|
303
305
|
function asRecord(value) {
|
|
304
306
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
305
307
|
}
|
|
308
|
+
function asStringRecord(value) {
|
|
309
|
+
const record = asRecord(value);
|
|
310
|
+
if (!record) return;
|
|
311
|
+
return Object.fromEntries(Object.entries(record).filter((entry) => typeof entry[1] === "string"));
|
|
312
|
+
}
|
|
306
313
|
function asStringArray(value) {
|
|
307
314
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
308
315
|
}
|
|
@@ -440,6 +447,7 @@ function createEditorState(cwd) {
|
|
|
440
447
|
invalidApply: "error",
|
|
441
448
|
invalidScreen: "error",
|
|
442
449
|
invalidVariant: "error",
|
|
450
|
+
deprecatedAtRule: "warning",
|
|
443
451
|
invalidConfigPath: "error",
|
|
444
452
|
invalidTailwindDirective: "error",
|
|
445
453
|
invalidSourceDirective: "error",
|
|
@@ -456,6 +464,7 @@ function createEditorState(cwd) {
|
|
|
456
464
|
capabilities: {
|
|
457
465
|
configuration: true,
|
|
458
466
|
diagnosticRelatedInformation: true,
|
|
467
|
+
diagnosticTagSupport: false,
|
|
459
468
|
itemDefaults: []
|
|
460
469
|
},
|
|
461
470
|
getConfiguration: async () => settings,
|
|
@@ -487,50 +496,40 @@ var AdapterLoadError = class extends Error {
|
|
|
487
496
|
//#endregion
|
|
488
497
|
//#region src/adapters/v3-adapter.ts
|
|
489
498
|
const require$4 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
490
|
-
|
|
499
|
+
function interopDefault(module) {
|
|
500
|
+
return module && typeof module === "object" && "default" in module ? module.default : module;
|
|
501
|
+
}
|
|
502
|
+
async function loadV3ClassMetadata(state, resolvePaths, verbose = false) {
|
|
491
503
|
try {
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
version: state.version || "unknown",
|
|
501
|
-
module: tailwindcss
|
|
502
|
-
},
|
|
503
|
-
jit: {
|
|
504
|
-
generateRules: { module: generateRulesModule.generateRules || ((_set, _context) => []) },
|
|
505
|
-
createContext: { module: contextUtils.createContext },
|
|
506
|
-
expandApplyAtRules: { module: generateRulesModule.expandApplyAtRules }
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
if (verbose) console.log(ansis.default.dim(" ✓ Loaded v3 JIT modules"));
|
|
510
|
-
} catch (jitError) {
|
|
511
|
-
if (verbose) {
|
|
512
|
-
const message = jitError instanceof Error ? jitError.message : String(jitError);
|
|
513
|
-
console.log(ansis.default.yellow(` ⚠ Warning: Could not load v3 JIT modules: ${message}`));
|
|
514
|
-
}
|
|
515
|
-
state.modules = { tailwindcss: {
|
|
504
|
+
const tailwindDir = node_path.dirname(require$4.resolve("tailwindcss/package.json", { paths: resolvePaths }));
|
|
505
|
+
const load = (id) => require$4(require$4.resolve(id, { paths: [tailwindDir] }));
|
|
506
|
+
const loadLib = (name) => require$4(node_path.join(tailwindDir, "lib", "lib", name));
|
|
507
|
+
const { createContext } = loadLib("setupContextUtils");
|
|
508
|
+
const { generateRules } = loadLib("generateRules");
|
|
509
|
+
state.jit = true;
|
|
510
|
+
state.modules = {
|
|
511
|
+
tailwindcss: {
|
|
516
512
|
version: state.version || "unknown",
|
|
517
|
-
module: tailwindcss
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (verbose) {
|
|
530
|
-
const message = contextError instanceof Error ? contextError.message : String(contextError);
|
|
531
|
-
console.log(ansis.default.yellow(` ⚠ Warning: Could not create JIT context: ${message}`));
|
|
513
|
+
module: load("tailwindcss")
|
|
514
|
+
},
|
|
515
|
+
postcss: {
|
|
516
|
+
version: load("postcss/package.json").version,
|
|
517
|
+
module: load("postcss")
|
|
518
|
+
},
|
|
519
|
+
postcssSelectorParser: { module: load("postcss-selector-parser") },
|
|
520
|
+
jit: {
|
|
521
|
+
generateRules: { module: generateRules },
|
|
522
|
+
createContext: { module: createContext },
|
|
523
|
+
expandApplyAtRules: { module: interopDefault(loadLib("expandApplyAtRules")) },
|
|
524
|
+
evaluateTailwindFunctions: { module: interopDefault(loadLib("evaluateTailwindFunctions")) }
|
|
532
525
|
}
|
|
533
|
-
}
|
|
526
|
+
};
|
|
527
|
+
extractConfigMetadata(state);
|
|
528
|
+
const jitContext = createContext(state.config);
|
|
529
|
+
state.jitContext = jitContext;
|
|
530
|
+
state.classList = jitContext.getClassList?.({ includeMetadata: true });
|
|
531
|
+
state.variants = jitContext.getVariants?.() ?? [];
|
|
532
|
+
if (verbose) console.log(ansis.default.dim(" ✓ Created v3 JIT context"));
|
|
534
533
|
} catch (error) {
|
|
535
534
|
if (error instanceof Error) throw new AdapterLoadError("v3", error);
|
|
536
535
|
throw new Error(`Failed to load v3 class metadata: ${String(error)}`);
|
|
@@ -542,37 +541,72 @@ function extractConfigMetadata(state) {
|
|
|
542
541
|
const theme = config.theme;
|
|
543
542
|
state.screens = Object.keys(theme?.screens ?? {});
|
|
544
543
|
state.blocklist = config.blocklist ?? [];
|
|
545
|
-
if (config.variants && typeof config.variants === "object") state.variants = Object.keys(config.variants).map((name) => ({
|
|
546
|
-
name,
|
|
547
|
-
values: [],
|
|
548
|
-
isArbitrary: false,
|
|
549
|
-
hasDash: true,
|
|
550
|
-
selectors: () => []
|
|
551
|
-
}));
|
|
552
544
|
if (config.corePlugins) state.corePlugins = Array.isArray(config.corePlugins) ? config.corePlugins : Object.keys(config.corePlugins);
|
|
553
545
|
}
|
|
554
546
|
//#endregion
|
|
555
547
|
//#region src/adapters/v4-adapter.ts
|
|
556
548
|
const require$3 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
557
|
-
|
|
549
|
+
function resolveModule(id, paths) {
|
|
550
|
+
let firstError;
|
|
551
|
+
for (const dir of paths) try {
|
|
552
|
+
return (0, node_module.createRequire)(node_path.join(dir, "noop.js")).resolve(id);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
firstError ??= error;
|
|
555
|
+
}
|
|
556
|
+
throw firstError ?? /* @__PURE__ */ new Error(`Cannot find module "${id}"`);
|
|
557
|
+
}
|
|
558
|
+
async function importModule(modulePath) {
|
|
559
|
+
let mod;
|
|
558
560
|
try {
|
|
559
|
-
|
|
561
|
+
mod = await import((0, node_url.pathToFileURL)(modulePath).href);
|
|
562
|
+
} catch {
|
|
563
|
+
mod = require$3(modulePath);
|
|
564
|
+
}
|
|
565
|
+
if (mod !== null && typeof mod === "object" && "default" in mod) return mod.default;
|
|
566
|
+
return mod;
|
|
567
|
+
}
|
|
568
|
+
function fromPostcss(nodes) {
|
|
569
|
+
return nodes.map((node) => {
|
|
570
|
+
switch (node.type) {
|
|
571
|
+
case "rule": return {
|
|
572
|
+
kind: "rule",
|
|
573
|
+
selector: node.selector,
|
|
574
|
+
nodes: fromPostcss(node.nodes)
|
|
575
|
+
};
|
|
576
|
+
case "atrule": return {
|
|
577
|
+
kind: "at-rule",
|
|
578
|
+
name: node.name,
|
|
579
|
+
params: node.params,
|
|
580
|
+
nodes: node.nodes ? fromPostcss(node.nodes) : null
|
|
581
|
+
};
|
|
582
|
+
case "decl": return {
|
|
583
|
+
kind: "declaration",
|
|
584
|
+
property: node.prop,
|
|
585
|
+
value: node.value,
|
|
586
|
+
important: node.important
|
|
587
|
+
};
|
|
588
|
+
default: return {
|
|
589
|
+
kind: "comment",
|
|
590
|
+
value: node.text
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
async function loadV4DesignSystem(state, resolvePaths, target, verbose = false) {
|
|
596
|
+
try {
|
|
597
|
+
const tailwindPath = require$3.resolve("tailwindcss", { paths: resolvePaths });
|
|
560
598
|
const tailwindcss = require$3(tailwindPath);
|
|
561
599
|
if (tailwindcss !== null && tailwindcss !== void 0 && (typeof tailwindcss === "object" || typeof tailwindcss === "function") && "__unstable__loadDesignSystem" in tailwindcss && typeof tailwindcss.__unstable__loadDesignSystem === "function") {
|
|
562
|
-
|
|
563
|
-
const basePath = node_path.dirname(configPath);
|
|
564
|
-
if (fileExists(configPath)) cssContent = readFileSync(configPath);
|
|
565
|
-
else cssContent = SYNTHETIC_VITE_CSS_CONFIG_CONTENT;
|
|
600
|
+
const cssContent = target.kind === "vite" ? SYNTHETIC_VITE_CSS_CONFIG_CONTENT : readFileSync(target.path);
|
|
566
601
|
const loadDesignSystem = tailwindcss.__unstable__loadDesignSystem;
|
|
567
602
|
const designSystem = await loadDesignSystem(cssContent, {
|
|
568
|
-
base:
|
|
603
|
+
base: node_path.dirname(target.path),
|
|
569
604
|
async loadModule(id, base, _type) {
|
|
570
605
|
try {
|
|
571
|
-
const modulePath =
|
|
572
|
-
const module = require$3(modulePath);
|
|
606
|
+
const modulePath = resolveModule(id, [base, ...resolvePaths]);
|
|
573
607
|
return {
|
|
574
608
|
base: node_path.dirname(modulePath),
|
|
575
|
-
module
|
|
609
|
+
module: await importModule(modulePath)
|
|
576
610
|
};
|
|
577
611
|
} catch (error) {
|
|
578
612
|
throw new Error(`Failed to load module "${id}": ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -584,7 +618,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
584
618
|
content
|
|
585
619
|
};
|
|
586
620
|
if (!_id.startsWith(".") && !_id.startsWith("/")) try {
|
|
587
|
-
const pkgJsonPath = require$3.resolve(`${_id}/package.json`, { paths: [base,
|
|
621
|
+
const pkgJsonPath = require$3.resolve(`${_id}/package.json`, { paths: [base, ...resolvePaths] });
|
|
588
622
|
const pkgDir = node_path.dirname(pkgJsonPath);
|
|
589
623
|
const cssPath = node_path.join(pkgDir, "index.css");
|
|
590
624
|
if (fileExists(cssPath)) return {
|
|
@@ -606,15 +640,16 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
606
640
|
Object.assign(designSystem, {
|
|
607
641
|
dependencies: () => /* @__PURE__ */ new Set(),
|
|
608
642
|
compile(classes) {
|
|
609
|
-
if (designSystem.
|
|
610
|
-
|
|
611
|
-
|
|
643
|
+
if (designSystem.candidatesToAst) return designSystem.candidatesToAst(classes);
|
|
644
|
+
if (designSystem.candidatesToCss) return designSystem.candidatesToCss(classes).map((css) => {
|
|
645
|
+
if (!css) return [];
|
|
646
|
+
try {
|
|
647
|
+
return fromPostcss(postcss.default.parse(css).nodes);
|
|
612
648
|
} catch {
|
|
613
|
-
return
|
|
649
|
+
return [];
|
|
614
650
|
}
|
|
615
|
-
return postcss.default.root();
|
|
616
651
|
});
|
|
617
|
-
return classes.map(() =>
|
|
652
|
+
return classes.map(() => []);
|
|
618
653
|
}
|
|
619
654
|
});
|
|
620
655
|
state.designSystem = designSystem;
|
|
@@ -632,6 +667,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
632
667
|
const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
633
668
|
const CONFIG_DISCOVERY_MAX_DEPTH = 8;
|
|
634
669
|
const PROJECT_ROOT_MARKERS = ["package.json", ".git"];
|
|
670
|
+
const VITE_CONFIG_NAME_REGEX = /^vite\.config\.[cm]?[jt]s$/;
|
|
635
671
|
const isCssConfigFile = (filePath) => filePath.endsWith(".css");
|
|
636
672
|
function findProjectRoot(startDir) {
|
|
637
673
|
let current = node_path.resolve(startDir);
|
|
@@ -642,9 +678,6 @@ function findProjectRoot(startDir) {
|
|
|
642
678
|
current = parent;
|
|
643
679
|
}
|
|
644
680
|
}
|
|
645
|
-
function syntheticViteCssConfigPath(viteConfigPath) {
|
|
646
|
-
return node_path.join(node_path.dirname(viteConfigPath), SYNTHETIC_VITE_CSS_CONFIG_NAME);
|
|
647
|
-
}
|
|
648
681
|
async function loadTailwindConfig(configPath) {
|
|
649
682
|
if (isCssConfigFile(configPath)) return {};
|
|
650
683
|
if (!node_path.isAbsolute(configPath)) throw new Error(`Config path must be absolute for security reasons: ${configPath}`);
|
|
@@ -661,66 +694,116 @@ async function loadTailwindConfig(configPath) {
|
|
|
661
694
|
throw new Error(`Failed to load config from ${configPath}: ${errorMessage}`);
|
|
662
695
|
}
|
|
663
696
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
cwd,
|
|
697
|
+
function toConfigTarget(configPath) {
|
|
698
|
+
return {
|
|
699
|
+
kind: configKind(configPath),
|
|
700
|
+
path: configPath,
|
|
701
|
+
root: findProjectRoot(node_path.dirname(configPath))
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function configKind(configPath) {
|
|
705
|
+
if (isCssConfigFile(configPath)) return "css";
|
|
706
|
+
return VITE_CONFIG_NAME_REGEX.test(node_path.basename(configPath)) ? "vite" : "js";
|
|
707
|
+
}
|
|
708
|
+
async function findTailwindConfigs(cwd) {
|
|
709
|
+
const root = node_path.resolve(cwd);
|
|
710
|
+
const files = await (0, tinyglobby.glob)([
|
|
711
|
+
"**/package.json",
|
|
712
|
+
"**/*.css",
|
|
713
|
+
...[...V3_CONFIG_PATHS, ...VITE_CONFIG_PATHS].map((p) => `**/${p}`)
|
|
714
|
+
], {
|
|
715
|
+
cwd: root,
|
|
683
716
|
absolute: true,
|
|
684
717
|
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
685
718
|
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
686
719
|
});
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
720
|
+
const isPackageJson = (file) => node_path.basename(file) === "package.json";
|
|
721
|
+
const packageRoots = await findPackageRoots(root, files.filter(isPackageJson).map(node_path.dirname));
|
|
722
|
+
const deepestFirst = [...packageRoots].sort((a, b) => b.length - a.length);
|
|
723
|
+
const candidatesByRoot = new Map(packageRoots.map((packageRoot) => [packageRoot, []]));
|
|
724
|
+
for (const file of files) {
|
|
725
|
+
if (isPackageJson(file)) continue;
|
|
726
|
+
const owner = deepestFirst.find((packageRoot) => isInsideDir(packageRoot, file));
|
|
727
|
+
if (owner) candidatesByRoot.get(owner)?.push(file);
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
targets: (await Promise.all([...candidatesByRoot].map(([packageRoot, candidates]) => pickConfig(packageRoot, candidates)))).filter((target) => target !== void 0),
|
|
731
|
+
packageRoots
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
async function findPackageRoots(root, packageDirs) {
|
|
735
|
+
const workspaces = await readWorkspacePatterns(root);
|
|
736
|
+
if (!workspaces) return [.../* @__PURE__ */ new Set([root, ...packageDirs])];
|
|
737
|
+
const toPackageJson = (pattern) => `${pattern.replace(/\/+$/, "")}/package.json`;
|
|
738
|
+
const workspacePackageJsons = await (0, tinyglobby.glob)(workspaces.filter((p) => !p.startsWith("!")).map(toPackageJson), {
|
|
739
|
+
cwd: root,
|
|
695
740
|
absolute: true,
|
|
696
|
-
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
697
|
-
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
741
|
+
ignore: [...DEFAULT_IGNORE_PATTERNS, ...workspaces.filter((p) => p.startsWith("!")).map((p) => toPackageJson(p.slice(1)))]
|
|
698
742
|
});
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
743
|
+
return [.../* @__PURE__ */ new Set([root, ...workspacePackageJsons.map(node_path.dirname)])];
|
|
744
|
+
}
|
|
745
|
+
async function readWorkspacePatterns(root) {
|
|
746
|
+
const [packageJson, pnpmWorkspace] = await Promise.all([readText(node_path.join(root, "package.json")), readText(node_path.join(root, "pnpm-workspace.yaml"))]);
|
|
747
|
+
const patterns = [...parsePackageJsonWorkspaces(packageJson), ...parsePnpmWorkspacePackages(pnpmWorkspace)];
|
|
748
|
+
return patterns.length > 0 ? patterns : void 0;
|
|
749
|
+
}
|
|
750
|
+
function parsePackageJsonWorkspaces(content) {
|
|
751
|
+
try {
|
|
752
|
+
const { workspaces } = JSON.parse(content);
|
|
753
|
+
return ((Array.isArray(workspaces) ? workspaces : workspaces?.packages) ?? []).filter((p) => typeof p === "string");
|
|
754
|
+
} catch {
|
|
755
|
+
return [];
|
|
708
756
|
}
|
|
709
|
-
const matches = (await (0, tinyglobby.glob)(VITE_CONFIG_PATHS.map((p) => `**/${p}`), {
|
|
710
|
-
cwd,
|
|
711
|
-
absolute: true,
|
|
712
|
-
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
713
|
-
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
714
|
-
})).filter(isTailwindViteConfig);
|
|
715
|
-
if (matches.length === 0) return null;
|
|
716
|
-
return sortByPathDepth(matches)[0];
|
|
717
757
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
758
|
+
function parsePnpmWorkspacePackages(content) {
|
|
759
|
+
const patterns = [];
|
|
760
|
+
let inPackages = false;
|
|
761
|
+
for (const line of content.split(/\r?\n/)) if (/^packages\s*:/.test(line)) inPackages = true;
|
|
762
|
+
else if (/^[^\s#]/.test(line)) inPackages = false;
|
|
763
|
+
else if (inPackages) {
|
|
764
|
+
const item = line.match(/^\s*-\s*["']?([^"'#\s]+)["']?/);
|
|
765
|
+
if (item) patterns.push(item[1]);
|
|
766
|
+
}
|
|
767
|
+
return patterns;
|
|
768
|
+
}
|
|
769
|
+
function nestedRootIgnores(root, roots) {
|
|
770
|
+
return roots.filter((other) => other !== root && isInsideDir(root, other)).map((other) => `${node_path.relative(root, other)}/**`);
|
|
771
|
+
}
|
|
772
|
+
function isInsideDir(dir, filePath) {
|
|
773
|
+
const relative = node_path.relative(dir, filePath);
|
|
774
|
+
return !relative.startsWith("..") && !node_path.isAbsolute(relative);
|
|
775
|
+
}
|
|
776
|
+
async function pickConfig(root, candidates) {
|
|
777
|
+
const target = (kind, configPath) => ({
|
|
778
|
+
kind,
|
|
779
|
+
path: configPath,
|
|
780
|
+
root
|
|
781
|
+
});
|
|
782
|
+
const candidateSet = new Set(candidates);
|
|
783
|
+
const existing = (paths) => paths.map((p) => node_path.join(root, p)).filter((p) => candidateSet.has(p));
|
|
784
|
+
const byName = (names) => candidates.filter((c) => names.includes(node_path.basename(c)));
|
|
785
|
+
const preferredCss = existing(V4_CSS_FOLDERS.flatMap((folder) => V4_CSS_NAMES.map((name) => node_path.join(folder, name))));
|
|
786
|
+
const preferredCssMatch = await findFirstMatch(preferredCss, TAILWIND_V4_IMPORT_REGEX);
|
|
787
|
+
if (preferredCssMatch) return target("css", preferredCssMatch);
|
|
788
|
+
const rootV3 = existing(V3_CONFIG_PATHS)[0];
|
|
789
|
+
if (rootV3) return target("js", rootV3);
|
|
790
|
+
const cssMatch = await findFirstMatch(sortCssCandidates(root, candidates.filter((c) => isCssConfigFile(c) && !preferredCss.includes(c))), TAILWIND_V4_IMPORT_REGEX);
|
|
791
|
+
if (cssMatch) return target("css", cssMatch);
|
|
792
|
+
const nestedV3 = sortByPathDepth(byName(V3_CONFIG_PATHS))[0];
|
|
793
|
+
if (nestedV3) return target("js", nestedV3);
|
|
794
|
+
const viteConfigs = [...existing(VITE_CONFIG_PATHS), ...sortByPathDepth(byName(VITE_CONFIG_PATHS))];
|
|
795
|
+
const viteMatch = await findFirstMatch([...new Set(viteConfigs)], TAILWIND_VITE_PLUGIN_REGEX);
|
|
796
|
+
if (viteMatch) return target("vite", viteMatch);
|
|
797
|
+
}
|
|
798
|
+
async function findFirstMatch(paths, regex) {
|
|
799
|
+
const contents = await Promise.all(paths.map(readText));
|
|
800
|
+
return paths.find((_, index) => regex.test(contents[index]));
|
|
801
|
+
}
|
|
802
|
+
async function readText(filePath) {
|
|
720
803
|
try {
|
|
721
|
-
return
|
|
804
|
+
return await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
722
805
|
} catch {
|
|
723
|
-
return
|
|
806
|
+
return "";
|
|
724
807
|
}
|
|
725
808
|
}
|
|
726
809
|
function sortByPathDepth(paths) {
|
|
@@ -758,9 +841,9 @@ function splitDepth(value) {
|
|
|
758
841
|
//#endregion
|
|
759
842
|
//#region src/state.ts
|
|
760
843
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
761
|
-
function getTailwindVersion(
|
|
844
|
+
function getTailwindVersion(paths) {
|
|
762
845
|
try {
|
|
763
|
-
const tailwindPackageJson = require$1.resolve("tailwindcss/package.json", { paths
|
|
846
|
+
const tailwindPackageJson = require$1.resolve("tailwindcss/package.json", { paths });
|
|
764
847
|
const { readFileSync } = require$1("node:fs");
|
|
765
848
|
return JSON.parse(readFileSync(tailwindPackageJson, "utf-8")).version;
|
|
766
849
|
} catch {
|
|
@@ -770,41 +853,52 @@ function getTailwindVersion(cwd) {
|
|
|
770
853
|
function isV4Config(version) {
|
|
771
854
|
return version?.startsWith("4.") ?? false;
|
|
772
855
|
}
|
|
773
|
-
function resolveTailwindPath(
|
|
774
|
-
const paths = configDir ? [configDir, cwd] : [cwd];
|
|
856
|
+
function resolveTailwindPath(paths) {
|
|
775
857
|
try {
|
|
776
858
|
return require$1.resolve("tailwindcss", { paths });
|
|
777
859
|
} catch {
|
|
778
860
|
throw new Error(`Could not find tailwindcss module in ${paths.join(" or ")}.\nInstall it with: npm install -D tailwindcss`);
|
|
779
861
|
}
|
|
780
862
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
863
|
+
function loadResolveConfig(paths) {
|
|
864
|
+
try {
|
|
865
|
+
const module = require$1(require$1.resolve("tailwindcss/resolveConfig", { paths }));
|
|
866
|
+
return typeof module === "function" ? module : module.default;
|
|
867
|
+
} catch {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const CONFIG_TYPE_LABELS = {
|
|
872
|
+
css: "CSS (v4)",
|
|
873
|
+
vite: "Vite (v4)",
|
|
874
|
+
js: "JavaScript"
|
|
875
|
+
};
|
|
876
|
+
async function createState(cwd, target, verbose = false) {
|
|
877
|
+
const isCssConfig = target.kind !== "js";
|
|
878
|
+
const resolvePaths = [node_path.dirname(target.path), cwd];
|
|
879
|
+
resolveTailwindPath(resolvePaths);
|
|
880
|
+
const version = getTailwindVersion(resolvePaths);
|
|
788
881
|
const isV4 = isV4Config(version);
|
|
789
882
|
if (verbose) {
|
|
790
883
|
console.log(ansis.default.cyan.bold("→ Tailwind Configuration"));
|
|
791
884
|
console.log(ansis.default.dim(` Version: ${version || "unknown"}`));
|
|
792
|
-
console.log(ansis.default.dim(` Config type: ${
|
|
793
|
-
console.log(ansis.default.dim(` Config path: ${
|
|
885
|
+
console.log(ansis.default.dim(` Config type: ${CONFIG_TYPE_LABELS[target.kind]}`));
|
|
886
|
+
console.log(ansis.default.dim(` Config path: ${target.path}`));
|
|
794
887
|
}
|
|
795
888
|
let config = {};
|
|
796
889
|
let resolvedConfig = { separator: ":" };
|
|
797
890
|
if (!isCssConfig) {
|
|
798
|
-
config = await loadTailwindConfig(
|
|
891
|
+
config = await loadTailwindConfig(target.path);
|
|
799
892
|
resolvedConfig = {
|
|
800
893
|
...config,
|
|
801
894
|
separator: config.separator ?? ":"
|
|
802
895
|
};
|
|
803
|
-
|
|
896
|
+
const resolveConfig = loadResolveConfig(resolvePaths);
|
|
897
|
+
if (resolveConfig) resolvedConfig = resolveConfig(config);
|
|
804
898
|
}
|
|
805
899
|
const state = {
|
|
806
900
|
enabled: true,
|
|
807
|
-
configPath:
|
|
901
|
+
configPath: target.path,
|
|
808
902
|
config: resolvedConfig,
|
|
809
903
|
version,
|
|
810
904
|
v4: isV4 || void 0,
|
|
@@ -818,14 +912,15 @@ async function createState(cwd, configPath, verbose = false) {
|
|
|
818
912
|
editor: createEditorState(cwd),
|
|
819
913
|
features: ["diagnostics"]
|
|
820
914
|
};
|
|
821
|
-
if (isV4 || isCssConfig) await loadV4DesignSystem(state,
|
|
822
|
-
else await loadV3ClassMetadata(state,
|
|
915
|
+
if (isV4 || isCssConfig) await loadV4DesignSystem(state, resolvePaths, target, verbose);
|
|
916
|
+
else await loadV3ClassMetadata(state, resolvePaths, verbose);
|
|
823
917
|
return state;
|
|
824
918
|
}
|
|
825
919
|
//#endregion
|
|
826
920
|
//#region src/linter.ts
|
|
827
921
|
const SOURCE_FILE_PATTERN = "**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}";
|
|
828
922
|
const GLOB_PATTERN_REGEX = /[*?[\]{}]/;
|
|
923
|
+
const MAX_LISTED_PACKAGES = 10;
|
|
829
924
|
async function validateDocument(state, filePath, content) {
|
|
830
925
|
try {
|
|
831
926
|
if (!state) throw new Error("State is not initialized");
|
|
@@ -833,7 +928,8 @@ async function validateDocument(state, filePath, content) {
|
|
|
833
928
|
if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
|
|
834
929
|
const languageId = getLanguageId(filePath);
|
|
835
930
|
const uri = `file://${filePath}`;
|
|
836
|
-
|
|
931
|
+
const document = vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content);
|
|
932
|
+
return await (0, _tailwindcss_language_service.doValidate)(state, document);
|
|
837
933
|
} catch (error) {
|
|
838
934
|
const message = error instanceof Error ? error.message : String(error);
|
|
839
935
|
if (message.includes("Cannot read") || message.includes("undefined")) {
|
|
@@ -844,52 +940,43 @@ async function validateDocument(state, filePath, content) {
|
|
|
844
940
|
throw new Error(`Failed to validate document ${filePath}: ${message}`);
|
|
845
941
|
}
|
|
846
942
|
}
|
|
847
|
-
async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
|
|
848
|
-
if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
|
|
849
|
-
return expandPatterns(cwd, patterns);
|
|
850
|
-
}
|
|
851
943
|
async function expandPatterns(cwd, patterns, extraIgnore = []) {
|
|
852
|
-
|
|
944
|
+
return (0, tinyglobby.glob)(patterns, {
|
|
853
945
|
cwd,
|
|
854
|
-
absolute:
|
|
946
|
+
absolute: true,
|
|
855
947
|
ignore: [...DEFAULT_IGNORE_PATTERNS, ...extraIgnore]
|
|
856
948
|
});
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
if (!configFilePath) throw new Error("Could not find Tailwind config for auto-discovery.\nUse --config to specify the path, or provide file patterns directly.");
|
|
862
|
-
const discoveryCwd = isCssConfigFile(configFilePath) ? findProjectRoot(node_path.dirname(configFilePath)) : cwd;
|
|
863
|
-
if (!isCssConfigFile(configFilePath)) {
|
|
864
|
-
const config = await loadTailwindConfig(configFilePath);
|
|
949
|
+
}
|
|
950
|
+
async function discoverFilesFromConfig(target, configRoots) {
|
|
951
|
+
if (target.kind === "js") {
|
|
952
|
+
const config = await loadTailwindConfig(target.path);
|
|
865
953
|
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}']");
|
|
866
954
|
const patterns = extractContentPatterns(config);
|
|
867
955
|
if (patterns.length === 0) throw new Error("No content patterns found in Tailwind config.\nEnsure your config has a content array with file patterns.");
|
|
868
|
-
return expandPatterns(
|
|
956
|
+
return expandPatterns(!Array.isArray(config.content) && config.content.relative === true ? node_path.dirname(target.path) : target.root, patterns);
|
|
869
957
|
}
|
|
870
|
-
const
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
if (!fileExists(configFilePath)) return files;
|
|
874
|
-
const relativeConfigPath = node_path.relative(discoveryCwd, configFilePath);
|
|
875
|
-
return [.../* @__PURE__ */ new Set([relativeConfigPath, ...files])].sort((a, b) => a.localeCompare(b));
|
|
876
|
-
};
|
|
877
|
-
const resolveDiscoveredFiles = (files) => discoveryCwd === cwd ? files : files.map((file) => node_path.resolve(discoveryCwd, file)).sort((a, b) => a.localeCompare(b));
|
|
958
|
+
const { root } = target;
|
|
959
|
+
const configDir = node_path.dirname(target.path);
|
|
960
|
+
const cssContent = target.kind === "vite" ? SYNTHETIC_VITE_CSS_CONFIG_CONTENT : readFileSync(target.path);
|
|
878
961
|
const { include, exclude } = extractSourcePatterns(cssContent);
|
|
879
962
|
const importSource = extractImportSourceDirectives(cssContent);
|
|
880
|
-
const resolveFromConfig = (pattern) =>
|
|
881
|
-
const absolutePattern = node_path.resolve(configDir, pattern);
|
|
882
|
-
return node_path.relative(discoveryCwd, absolutePattern);
|
|
883
|
-
};
|
|
963
|
+
const resolveFromConfig = (pattern) => node_path.relative(root, node_path.resolve(configDir, pattern));
|
|
884
964
|
const resolvedExclude = exclude.map(resolveFromConfig);
|
|
885
|
-
const
|
|
886
|
-
|
|
965
|
+
const extraIgnore = [
|
|
966
|
+
...resolvedExclude,
|
|
967
|
+
...readGitignorePatterns(root),
|
|
968
|
+
...nestedRootIgnores(root, configRoots)
|
|
969
|
+
];
|
|
887
970
|
const explicitSourcePatterns = include.map((source) => resolveFromConfig(normalizeSourcePattern(source)));
|
|
888
971
|
let autoPatterns = [];
|
|
889
|
-
if (!importSource.disableAutoSource) autoPatterns = importSource.roots.length > 0 ? importSource.roots.map((
|
|
890
|
-
const autoFiles = autoPatterns.length > 0 ?
|
|
891
|
-
const
|
|
892
|
-
return
|
|
972
|
+
if (!importSource.disableAutoSource) autoPatterns = importSource.roots.length > 0 ? importSource.roots.map((sourceRoot) => resolveFromConfig(normalizeSourcePattern(sourceRoot))) : [DEFAULT_FILE_PATTERN];
|
|
973
|
+
const [autoFiles, explicitFiles] = await Promise.all([autoPatterns.length > 0 ? expandPatterns(root, autoPatterns, extraIgnore) : [], explicitSourcePatterns.length > 0 ? expandPatterns(root, explicitSourcePatterns, resolvedExclude) : []]);
|
|
974
|
+
const configFile = target.kind === "css" ? [target.path] : [];
|
|
975
|
+
return [.../* @__PURE__ */ new Set([
|
|
976
|
+
...configFile,
|
|
977
|
+
...autoFiles,
|
|
978
|
+
...explicitFiles
|
|
979
|
+
])];
|
|
893
980
|
}
|
|
894
981
|
function normalizeSourcePattern(pattern) {
|
|
895
982
|
if (GLOB_PATTERN_REGEX.test(pattern) || node_path.extname(pattern)) return pattern;
|
|
@@ -965,9 +1052,9 @@ async function processFile(state, cwd, filePath, fix) {
|
|
|
965
1052
|
fixedCount
|
|
966
1053
|
};
|
|
967
1054
|
}
|
|
968
|
-
async function initializeState(cwd,
|
|
1055
|
+
async function initializeState(cwd, target, verbose = false) {
|
|
969
1056
|
try {
|
|
970
|
-
const state = await createState(cwd,
|
|
1057
|
+
const state = await createState(cwd, target, verbose);
|
|
971
1058
|
if (verbose) console.log();
|
|
972
1059
|
return state;
|
|
973
1060
|
} catch (error) {
|
|
@@ -976,42 +1063,80 @@ async function initializeState(cwd, configPath, verbose = false) {
|
|
|
976
1063
|
}
|
|
977
1064
|
}
|
|
978
1065
|
async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
const
|
|
1066
|
+
const targets = await resolveConfigTargets(cwd, configPath);
|
|
1067
|
+
if (verbose) logTargets(cwd, targets);
|
|
1068
|
+
const configRoots = targets.map((target) => target.root);
|
|
1069
|
+
const patternFiles = autoDiscover ? [] : await expandPatterns(cwd, patterns);
|
|
1070
|
+
const discoveredFilesByTarget = await Promise.all(targets.map((target) => autoDiscover ? discoverFilesFromConfig(target, configRoots) : patternFiles));
|
|
1071
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1072
|
+
for (const [index, target] of targets.entries()) {
|
|
1073
|
+
const files = target.kind === "css" && !autoDiscover ? [target.path, ...discoveredFilesByTarget[index]] : discoveredFilesByTarget[index];
|
|
1074
|
+
for (const file of files) {
|
|
1075
|
+
const current = owners.get(file);
|
|
1076
|
+
if (!current || ownershipScore(target, file) > ownershipScore(current, file)) owners.set(file, target);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
982
1079
|
const skippedFiles = [];
|
|
983
|
-
const
|
|
984
|
-
|
|
1080
|
+
const filesByTarget = /* @__PURE__ */ new Map();
|
|
1081
|
+
for (const [absolutePath, target] of owners) {
|
|
1082
|
+
const file = node_path.relative(cwd, absolutePath);
|
|
1083
|
+
if (getFileSize(absolutePath) > 524288) {
|
|
985
1084
|
skippedFiles.push(file);
|
|
986
|
-
|
|
1085
|
+
continue;
|
|
987
1086
|
}
|
|
988
|
-
|
|
989
|
-
|
|
1087
|
+
const files = filesByTarget.get(target) ?? [];
|
|
1088
|
+
files.push(file);
|
|
1089
|
+
filesByTarget.set(target, files);
|
|
1090
|
+
}
|
|
1091
|
+
const totalFiles = owners.size - skippedFiles.length;
|
|
990
1092
|
if (verbose) {
|
|
991
|
-
console.log(ansis.default.cyan.bold(`→ Discovered ${
|
|
1093
|
+
console.log(ansis.default.cyan.bold(`→ Discovered ${totalFiles} file${totalFiles !== 1 ? "s" : ""} to lint`));
|
|
992
1094
|
console.log();
|
|
993
1095
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1096
|
+
const results = [];
|
|
1097
|
+
let processedCount = 0;
|
|
1098
|
+
for (const target of targets) {
|
|
1099
|
+
const files = filesByTarget.get(target);
|
|
1100
|
+
if (!files) continue;
|
|
1101
|
+
const offset = processedCount;
|
|
1102
|
+
processedCount += files.length;
|
|
1103
|
+
const processed = await processFiles(await initializeState(cwd, target, verbose), cwd, files.sort((a, b) => a.localeCompare(b)), fix, onProgress && ((current, _total, file) => onProgress(offset + current, totalFiles, file)));
|
|
1104
|
+
results.push(...processed);
|
|
1105
|
+
}
|
|
999
1106
|
return {
|
|
1000
|
-
files:
|
|
1001
|
-
totalFilesProcessed:
|
|
1002
|
-
skippedFiles
|
|
1107
|
+
files: results.filter((result) => result.diagnostics.length > 0 || result.fixed).sort((a, b) => a.path.localeCompare(b.path)),
|
|
1108
|
+
totalFilesProcessed: totalFiles,
|
|
1109
|
+
skippedFiles: skippedFiles.sort((a, b) => a.localeCompare(b))
|
|
1003
1110
|
};
|
|
1004
1111
|
}
|
|
1005
|
-
function
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
if (seen.has(absolutePath)) continue;
|
|
1011
|
-
seen.add(absolutePath);
|
|
1012
|
-
unique.push(file);
|
|
1112
|
+
async function resolveConfigTargets(cwd, configPath) {
|
|
1113
|
+
if (configPath) {
|
|
1114
|
+
const absolutePath = node_path.resolve(cwd, configPath);
|
|
1115
|
+
if (!fileExists(absolutePath)) throw new Error(`Tailwind config file not found: ${absolutePath}`);
|
|
1116
|
+
return [toConfigTarget(absolutePath)];
|
|
1013
1117
|
}
|
|
1014
|
-
|
|
1118
|
+
const { targets, packageRoots } = await findTailwindConfigs(cwd);
|
|
1119
|
+
if (targets.length === 0) throw new Error(noConfigMessage(cwd, packageRoots));
|
|
1120
|
+
return targets;
|
|
1121
|
+
}
|
|
1122
|
+
function noConfigMessage(cwd, packageRoots) {
|
|
1123
|
+
const packages = packageRoots.map((root) => node_path.relative(cwd, root)).filter(Boolean).sort((a, b) => a.localeCompare(b));
|
|
1124
|
+
const shown = packages.slice(0, MAX_LISTED_PACKAGES);
|
|
1125
|
+
const more = packages.length - shown.length;
|
|
1126
|
+
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:
|
|
1127
|
+
• Tailwind v4 (CSS): a CSS file with @import "tailwindcss", e.g. app.css or src/index.css
|
|
1128
|
+
• Tailwind v4 (Vite): vite.config.* with @tailwindcss/vite
|
|
1129
|
+
• Tailwind v3 (JS): tailwind.config.js, tailwind.config.ts
|
|
1130
|
+
Use --config to specify the path.`;
|
|
1131
|
+
}
|
|
1132
|
+
function logTargets(cwd, targets) {
|
|
1133
|
+
const display = (p) => node_path.relative(cwd, p) || ".";
|
|
1134
|
+
console.log(ansis.default.cyan.bold("→ Tailwind configs"));
|
|
1135
|
+
for (const target of targets) console.log(ansis.default.dim(` ${display(target.root)} → ${display(target.path)}`));
|
|
1136
|
+
console.log();
|
|
1137
|
+
}
|
|
1138
|
+
function ownershipScore(target, absolutePath) {
|
|
1139
|
+
return isInsideDir(target.root, absolutePath) ? target.root.length : -1;
|
|
1015
1140
|
}
|
|
1016
1141
|
//#endregion
|
|
1017
1142
|
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-B-Bi_uRJ.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,6 +383,6 @@ declare function extractImportSourceDirectives(cssContent: string): {
|
|
|
383
383
|
roots: string[];
|
|
384
384
|
disableAutoSource: boolean;
|
|
385
385
|
};
|
|
386
|
-
declare function lint({ cwd, patterns, configPath, autoDiscover, fix, verbose, onProgress }: LintOptions): Promise<LintResult>;
|
|
386
|
+
export declare function lint({ cwd, patterns, configPath, autoDiscover, fix, verbose, onProgress }: LintOptions): Promise<LintResult>;
|
|
387
387
|
//#endregion
|
|
388
|
-
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.1",
|
|
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": "^26.
|
|
70
|
-
"oxfmt": "^0.
|
|
71
|
-
"oxlint": "^1.
|
|
72
|
-
"tsdown": "^0.
|
|
69
|
+
"@types/node": "^26.6.4",
|
|
70
|
+
"oxfmt": "^0.71.0",
|
|
71
|
+
"oxlint": "^1.86.0",
|
|
72
|
+
"tsdown": "^0.23.0",
|
|
73
73
|
"typescript": "^7.0.2",
|
|
74
|
-
"vitest": "^
|
|
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
|
}
|