tailwind-lint 0.12.1 → 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-BP0uKBfa.cjs → linter-gjoSGNMw.cjs} +308 -202
- 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-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",
|
|
@@ -134,7 +134,7 @@ async function getQuickfixes(state, document, uri, diagnostics) {
|
|
|
134
134
|
code: diag.code,
|
|
135
135
|
source: diag.source || "tailwindcss"
|
|
136
136
|
}));
|
|
137
|
-
|
|
137
|
+
const params = {
|
|
138
138
|
textDocument: { uri },
|
|
139
139
|
range: {
|
|
140
140
|
start: {
|
|
@@ -147,7 +147,8 @@ async function getQuickfixes(state, document, uri, diagnostics) {
|
|
|
147
147
|
}
|
|
148
148
|
},
|
|
149
149
|
context: { diagnostics: lspDiagnostics }
|
|
150
|
-
}
|
|
150
|
+
};
|
|
151
|
+
return (await (0, _tailwindcss_language_service.doCodeActions)(state, params, document)).filter((action) => action.kind === "quickfix" || action.kind?.startsWith(`quickfix.`));
|
|
151
152
|
}
|
|
152
153
|
function applyFirstQuickfix(action, uri, document, content) {
|
|
153
154
|
if (!action.edit?.changes?.[uri]) return null;
|
|
@@ -257,7 +258,7 @@ function readVsCodeTailwindSettings(cwd) {
|
|
|
257
258
|
const files = readNestedObject(raw, "tailwindCSS.files");
|
|
258
259
|
const lint = readNestedObject(raw, "tailwindCSS.lint");
|
|
259
260
|
return {
|
|
260
|
-
includeLanguages:
|
|
261
|
+
includeLanguages: asStringRecord(raw["tailwindCSS.includeLanguages"]),
|
|
261
262
|
classAttributes: asStringArray(raw["tailwindCSS.classAttributes"]),
|
|
262
263
|
classFunctions: asStringArray(raw["tailwindCSS.classFunctions"]),
|
|
263
264
|
experimental: {
|
|
@@ -275,7 +276,7 @@ function readZedTailwindSettings(cwd) {
|
|
|
275
276
|
const normalized = asRecord(serverSettings?.tailwindCSS) ?? serverSettings;
|
|
276
277
|
if (!normalized) return {};
|
|
277
278
|
return {
|
|
278
|
-
includeLanguages:
|
|
279
|
+
includeLanguages: asStringRecord(normalized.includeLanguages),
|
|
279
280
|
classAttributes: asStringArray(normalized.classAttributes),
|
|
280
281
|
classFunctions: asStringArray(normalized.classFunctions),
|
|
281
282
|
experimental: {
|
|
@@ -303,6 +304,11 @@ function readNestedObject(input, key) {
|
|
|
303
304
|
function asRecord(value) {
|
|
304
305
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
305
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
|
+
}
|
|
306
312
|
function asStringArray(value) {
|
|
307
313
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
308
314
|
}
|
|
@@ -440,6 +446,7 @@ function createEditorState(cwd) {
|
|
|
440
446
|
invalidApply: "error",
|
|
441
447
|
invalidScreen: "error",
|
|
442
448
|
invalidVariant: "error",
|
|
449
|
+
deprecatedAtRule: "warning",
|
|
443
450
|
invalidConfigPath: "error",
|
|
444
451
|
invalidTailwindDirective: "error",
|
|
445
452
|
invalidSourceDirective: "error",
|
|
@@ -456,6 +463,7 @@ function createEditorState(cwd) {
|
|
|
456
463
|
capabilities: {
|
|
457
464
|
configuration: true,
|
|
458
465
|
diagnosticRelatedInformation: true,
|
|
466
|
+
diagnosticTagSupport: false,
|
|
459
467
|
itemDefaults: []
|
|
460
468
|
},
|
|
461
469
|
getConfiguration: async () => settings,
|
|
@@ -487,50 +495,40 @@ var AdapterLoadError = class extends Error {
|
|
|
487
495
|
//#endregion
|
|
488
496
|
//#region src/adapters/v3-adapter.ts
|
|
489
497
|
const require$4 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
490
|
-
|
|
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) {
|
|
491
502
|
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: {
|
|
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: {
|
|
516
511
|
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}`));
|
|
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")) }
|
|
532
524
|
}
|
|
533
|
-
}
|
|
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"));
|
|
534
532
|
} catch (error) {
|
|
535
533
|
if (error instanceof Error) throw new AdapterLoadError("v3", error);
|
|
536
534
|
throw new Error(`Failed to load v3 class metadata: ${String(error)}`);
|
|
@@ -542,33 +540,50 @@ function extractConfigMetadata(state) {
|
|
|
542
540
|
const theme = config.theme;
|
|
543
541
|
state.screens = Object.keys(theme?.screens ?? {});
|
|
544
542
|
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
543
|
if (config.corePlugins) state.corePlugins = Array.isArray(config.corePlugins) ? config.corePlugins : Object.keys(config.corePlugins);
|
|
553
544
|
}
|
|
554
545
|
//#endregion
|
|
555
546
|
//#region src/adapters/v4-adapter.ts
|
|
556
547
|
const require$3 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
557
|
-
|
|
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) {
|
|
558
576
|
try {
|
|
559
|
-
const tailwindPath = require$3.resolve("tailwindcss", { paths:
|
|
577
|
+
const tailwindPath = require$3.resolve("tailwindcss", { paths: resolvePaths });
|
|
560
578
|
const tailwindcss = require$3(tailwindPath);
|
|
561
579
|
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;
|
|
580
|
+
const cssContent = target.kind === "vite" ? SYNTHETIC_VITE_CSS_CONFIG_CONTENT : readFileSync(target.path);
|
|
566
581
|
const loadDesignSystem = tailwindcss.__unstable__loadDesignSystem;
|
|
567
582
|
const designSystem = await loadDesignSystem(cssContent, {
|
|
568
|
-
base:
|
|
583
|
+
base: node_path.dirname(target.path),
|
|
569
584
|
async loadModule(id, base, _type) {
|
|
570
585
|
try {
|
|
571
|
-
const modulePath = require$3.resolve(id, { paths: [base,
|
|
586
|
+
const modulePath = require$3.resolve(id, { paths: [base, ...resolvePaths] });
|
|
572
587
|
const module = require$3(modulePath);
|
|
573
588
|
return {
|
|
574
589
|
base: node_path.dirname(modulePath),
|
|
@@ -584,7 +599,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
584
599
|
content
|
|
585
600
|
};
|
|
586
601
|
if (!_id.startsWith(".") && !_id.startsWith("/")) try {
|
|
587
|
-
const pkgJsonPath = require$3.resolve(`${_id}/package.json`, { paths: [base,
|
|
602
|
+
const pkgJsonPath = require$3.resolve(`${_id}/package.json`, { paths: [base, ...resolvePaths] });
|
|
588
603
|
const pkgDir = node_path.dirname(pkgJsonPath);
|
|
589
604
|
const cssPath = node_path.join(pkgDir, "index.css");
|
|
590
605
|
if (fileExists(cssPath)) return {
|
|
@@ -606,15 +621,16 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
606
621
|
Object.assign(designSystem, {
|
|
607
622
|
dependencies: () => /* @__PURE__ */ new Set(),
|
|
608
623
|
compile(classes) {
|
|
609
|
-
if (designSystem.
|
|
610
|
-
|
|
611
|
-
|
|
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);
|
|
612
629
|
} catch {
|
|
613
|
-
return
|
|
630
|
+
return [];
|
|
614
631
|
}
|
|
615
|
-
return postcss.default.root();
|
|
616
632
|
});
|
|
617
|
-
return classes.map(() =>
|
|
633
|
+
return classes.map(() => []);
|
|
618
634
|
}
|
|
619
635
|
});
|
|
620
636
|
state.designSystem = designSystem;
|
|
@@ -632,6 +648,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
|
|
|
632
648
|
const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
633
649
|
const CONFIG_DISCOVERY_MAX_DEPTH = 8;
|
|
634
650
|
const PROJECT_ROOT_MARKERS = ["package.json", ".git"];
|
|
651
|
+
const VITE_CONFIG_NAME_REGEX = /^vite\.config\.[cm]?[jt]s$/;
|
|
635
652
|
const isCssConfigFile = (filePath) => filePath.endsWith(".css");
|
|
636
653
|
function findProjectRoot(startDir) {
|
|
637
654
|
let current = node_path.resolve(startDir);
|
|
@@ -642,9 +659,6 @@ function findProjectRoot(startDir) {
|
|
|
642
659
|
current = parent;
|
|
643
660
|
}
|
|
644
661
|
}
|
|
645
|
-
function syntheticViteCssConfigPath(viteConfigPath) {
|
|
646
|
-
return node_path.join(node_path.dirname(viteConfigPath), SYNTHETIC_VITE_CSS_CONFIG_NAME);
|
|
647
|
-
}
|
|
648
662
|
async function loadTailwindConfig(configPath) {
|
|
649
663
|
if (isCssConfigFile(configPath)) return {};
|
|
650
664
|
if (!node_path.isAbsolute(configPath)) throw new Error(`Config path must be absolute for security reasons: ${configPath}`);
|
|
@@ -661,66 +675,116 @@ async function loadTailwindConfig(configPath) {
|
|
|
661
675
|
throw new Error(`Failed to load config from ${configPath}: ${errorMessage}`);
|
|
662
676
|
}
|
|
663
677
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
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,
|
|
683
697
|
absolute: true,
|
|
684
698
|
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
685
699
|
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
686
700
|
});
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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,
|
|
695
721
|
absolute: true,
|
|
696
|
-
ignore: DEFAULT_IGNORE_PATTERNS,
|
|
697
|
-
deep: CONFIG_DISCOVERY_MAX_DEPTH
|
|
722
|
+
ignore: [...DEFAULT_IGNORE_PATTERNS, ...workspaces.filter((p) => p.startsWith("!")).map((p) => toPackageJson(p.slice(1)))]
|
|
698
723
|
});
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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 [];
|
|
708
737
|
}
|
|
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
738
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
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) {
|
|
720
784
|
try {
|
|
721
|
-
return
|
|
785
|
+
return await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
722
786
|
} catch {
|
|
723
|
-
return
|
|
787
|
+
return "";
|
|
724
788
|
}
|
|
725
789
|
}
|
|
726
790
|
function sortByPathDepth(paths) {
|
|
@@ -758,9 +822,9 @@ function splitDepth(value) {
|
|
|
758
822
|
//#endregion
|
|
759
823
|
//#region src/state.ts
|
|
760
824
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
|
|
761
|
-
function getTailwindVersion(
|
|
825
|
+
function getTailwindVersion(paths) {
|
|
762
826
|
try {
|
|
763
|
-
const tailwindPackageJson = require$1.resolve("tailwindcss/package.json", { paths
|
|
827
|
+
const tailwindPackageJson = require$1.resolve("tailwindcss/package.json", { paths });
|
|
764
828
|
const { readFileSync } = require$1("node:fs");
|
|
765
829
|
return JSON.parse(readFileSync(tailwindPackageJson, "utf-8")).version;
|
|
766
830
|
} catch {
|
|
@@ -770,41 +834,52 @@ function getTailwindVersion(cwd) {
|
|
|
770
834
|
function isV4Config(version) {
|
|
771
835
|
return version?.startsWith("4.") ?? false;
|
|
772
836
|
}
|
|
773
|
-
function resolveTailwindPath(
|
|
774
|
-
const paths = configDir ? [configDir, cwd] : [cwd];
|
|
837
|
+
function resolveTailwindPath(paths) {
|
|
775
838
|
try {
|
|
776
839
|
return require$1.resolve("tailwindcss", { paths });
|
|
777
840
|
} catch {
|
|
778
841
|
throw new Error(`Could not find tailwindcss module in ${paths.join(" or ")}.\nInstall it with: npm install -D tailwindcss`);
|
|
779
842
|
}
|
|
780
843
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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);
|
|
788
862
|
const isV4 = isV4Config(version);
|
|
789
863
|
if (verbose) {
|
|
790
864
|
console.log(ansis.default.cyan.bold("→ Tailwind Configuration"));
|
|
791
865
|
console.log(ansis.default.dim(` Version: ${version || "unknown"}`));
|
|
792
|
-
console.log(ansis.default.dim(` Config type: ${
|
|
793
|
-
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}`));
|
|
794
868
|
}
|
|
795
869
|
let config = {};
|
|
796
870
|
let resolvedConfig = { separator: ":" };
|
|
797
871
|
if (!isCssConfig) {
|
|
798
|
-
config = await loadTailwindConfig(
|
|
872
|
+
config = await loadTailwindConfig(target.path);
|
|
799
873
|
resolvedConfig = {
|
|
800
874
|
...config,
|
|
801
875
|
separator: config.separator ?? ":"
|
|
802
876
|
};
|
|
803
|
-
|
|
877
|
+
const resolveConfig = loadResolveConfig(resolvePaths);
|
|
878
|
+
if (resolveConfig) resolvedConfig = resolveConfig(config);
|
|
804
879
|
}
|
|
805
880
|
const state = {
|
|
806
881
|
enabled: true,
|
|
807
|
-
configPath:
|
|
882
|
+
configPath: target.path,
|
|
808
883
|
config: resolvedConfig,
|
|
809
884
|
version,
|
|
810
885
|
v4: isV4 || void 0,
|
|
@@ -818,14 +893,15 @@ async function createState(cwd, configPath, verbose = false) {
|
|
|
818
893
|
editor: createEditorState(cwd),
|
|
819
894
|
features: ["diagnostics"]
|
|
820
895
|
};
|
|
821
|
-
if (isV4 || isCssConfig) await loadV4DesignSystem(state,
|
|
822
|
-
else await loadV3ClassMetadata(state,
|
|
896
|
+
if (isV4 || isCssConfig) await loadV4DesignSystem(state, resolvePaths, target, verbose);
|
|
897
|
+
else await loadV3ClassMetadata(state, resolvePaths, verbose);
|
|
823
898
|
return state;
|
|
824
899
|
}
|
|
825
900
|
//#endregion
|
|
826
901
|
//#region src/linter.ts
|
|
827
902
|
const SOURCE_FILE_PATTERN = "**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}";
|
|
828
903
|
const GLOB_PATTERN_REGEX = /[*?[\]{}]/;
|
|
904
|
+
const MAX_LISTED_PACKAGES = 10;
|
|
829
905
|
async function validateDocument(state, filePath, content) {
|
|
830
906
|
try {
|
|
831
907
|
if (!state) throw new Error("State is not initialized");
|
|
@@ -833,7 +909,8 @@ async function validateDocument(state, filePath, content) {
|
|
|
833
909
|
if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
|
|
834
910
|
const languageId = getLanguageId(filePath);
|
|
835
911
|
const uri = `file://${filePath}`;
|
|
836
|
-
|
|
912
|
+
const document = vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content);
|
|
913
|
+
return await (0, _tailwindcss_language_service.doValidate)(state, document);
|
|
837
914
|
} catch (error) {
|
|
838
915
|
const message = error instanceof Error ? error.message : String(error);
|
|
839
916
|
if (message.includes("Cannot read") || message.includes("undefined")) {
|
|
@@ -844,52 +921,43 @@ async function validateDocument(state, filePath, content) {
|
|
|
844
921
|
throw new Error(`Failed to validate document ${filePath}: ${message}`);
|
|
845
922
|
}
|
|
846
923
|
}
|
|
847
|
-
async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
|
|
848
|
-
if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
|
|
849
|
-
return expandPatterns(cwd, patterns);
|
|
850
|
-
}
|
|
851
924
|
async function expandPatterns(cwd, patterns, extraIgnore = []) {
|
|
852
|
-
|
|
925
|
+
return (0, tinyglobby.glob)(patterns, {
|
|
853
926
|
cwd,
|
|
854
|
-
absolute:
|
|
927
|
+
absolute: true,
|
|
855
928
|
ignore: [...DEFAULT_IGNORE_PATTERNS, ...extraIgnore]
|
|
856
929
|
});
|
|
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);
|
|
930
|
+
}
|
|
931
|
+
async function discoverFilesFromConfig(target, configRoots) {
|
|
932
|
+
if (target.kind === "js") {
|
|
933
|
+
const config = await loadTailwindConfig(target.path);
|
|
865
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}']");
|
|
866
935
|
const patterns = extractContentPatterns(config);
|
|
867
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.");
|
|
868
|
-
return expandPatterns(
|
|
937
|
+
return expandPatterns(!Array.isArray(config.content) && config.content.relative === true ? node_path.dirname(target.path) : target.root, patterns);
|
|
869
938
|
}
|
|
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));
|
|
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);
|
|
878
942
|
const { include, exclude } = extractSourcePatterns(cssContent);
|
|
879
943
|
const importSource = extractImportSourceDirectives(cssContent);
|
|
880
|
-
const resolveFromConfig = (pattern) =>
|
|
881
|
-
const absolutePattern = node_path.resolve(configDir, pattern);
|
|
882
|
-
return node_path.relative(discoveryCwd, absolutePattern);
|
|
883
|
-
};
|
|
944
|
+
const resolveFromConfig = (pattern) => node_path.relative(root, node_path.resolve(configDir, pattern));
|
|
884
945
|
const resolvedExclude = exclude.map(resolveFromConfig);
|
|
885
|
-
const
|
|
886
|
-
|
|
946
|
+
const extraIgnore = [
|
|
947
|
+
...resolvedExclude,
|
|
948
|
+
...readGitignorePatterns(root),
|
|
949
|
+
...nestedRootIgnores(root, configRoots)
|
|
950
|
+
];
|
|
887
951
|
const explicitSourcePatterns = include.map((source) => resolveFromConfig(normalizeSourcePattern(source)));
|
|
888
952
|
let autoPatterns = [];
|
|
889
|
-
if (!importSource.disableAutoSource) autoPatterns = importSource.roots.length > 0 ? importSource.roots.map((
|
|
890
|
-
const autoFiles = autoPatterns.length > 0 ?
|
|
891
|
-
const
|
|
892
|
-
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
|
+
])];
|
|
893
961
|
}
|
|
894
962
|
function normalizeSourcePattern(pattern) {
|
|
895
963
|
if (GLOB_PATTERN_REGEX.test(pattern) || node_path.extname(pattern)) return pattern;
|
|
@@ -965,9 +1033,9 @@ async function processFile(state, cwd, filePath, fix) {
|
|
|
965
1033
|
fixedCount
|
|
966
1034
|
};
|
|
967
1035
|
}
|
|
968
|
-
async function initializeState(cwd,
|
|
1036
|
+
async function initializeState(cwd, target, verbose = false) {
|
|
969
1037
|
try {
|
|
970
|
-
const state = await createState(cwd,
|
|
1038
|
+
const state = await createState(cwd, target, verbose);
|
|
971
1039
|
if (verbose) console.log();
|
|
972
1040
|
return state;
|
|
973
1041
|
} catch (error) {
|
|
@@ -976,42 +1044,80 @@ async function initializeState(cwd, configPath, verbose = false) {
|
|
|
976
1044
|
}
|
|
977
1045
|
}
|
|
978
1046
|
async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
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
|
+
}
|
|
982
1060
|
const skippedFiles = [];
|
|
983
|
-
const
|
|
984
|
-
|
|
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) {
|
|
985
1065
|
skippedFiles.push(file);
|
|
986
|
-
|
|
1066
|
+
continue;
|
|
987
1067
|
}
|
|
988
|
-
|
|
989
|
-
|
|
1068
|
+
const files = filesByTarget.get(target) ?? [];
|
|
1069
|
+
files.push(file);
|
|
1070
|
+
filesByTarget.set(target, files);
|
|
1071
|
+
}
|
|
1072
|
+
const totalFiles = owners.size - skippedFiles.length;
|
|
990
1073
|
if (verbose) {
|
|
991
|
-
console.log(ansis.default.cyan.bold(`→ Discovered ${
|
|
1074
|
+
console.log(ansis.default.cyan.bold(`→ Discovered ${totalFiles} file${totalFiles !== 1 ? "s" : ""} to lint`));
|
|
992
1075
|
console.log();
|
|
993
1076
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
+
}
|
|
999
1087
|
return {
|
|
1000
|
-
files:
|
|
1001
|
-
totalFilesProcessed:
|
|
1002
|
-
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))
|
|
1003
1091
|
};
|
|
1004
1092
|
}
|
|
1005
|
-
function
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
if (seen.has(absolutePath)) continue;
|
|
1011
|
-
seen.add(absolutePath);
|
|
1012
|
-
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)];
|
|
1013
1098
|
}
|
|
1014
|
-
|
|
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;
|
|
1015
1121
|
}
|
|
1016
1122
|
//#endregion
|
|
1017
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,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.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": "^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
|
}
|