code-auditor-mcp 3.4.1 → 3.4.3
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +204 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +68 -35
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts +15 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.js +35 -2
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +27 -8
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +19 -12
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +55 -53
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +9 -0
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +45 -0
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +23 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +122 -37
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/auditRunner.d.ts +4 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +42 -22
- package/dist/auditRunner.js.map +1 -1
- package/dist/codeIndexDB.d.ts +1 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +18 -4
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +4 -3
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -18
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +10 -21
- package/dist/config/defaults.js.map +1 -1
- package/dist/conventions/conventionMiner.d.ts +12 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +157 -48
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +2 -0
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +7 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +9 -2
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +9 -1
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +13 -6
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +5 -0
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/scripts/runBench.d.ts.map +1 -1
- package/dist/scripts/runBench.js +16 -4
- package/dist/scripts/runBench.js.map +1 -1
- package/dist/styles/styleExtractor.js +6 -3
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.js +9 -3
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +101 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -0
- package/dist/styles/tailwindProbe.js +637 -0
- package/dist/styles/tailwindProbe.js.map +1 -0
- package/dist/styles/tailwindUtilityExpander.d.ts +140 -0
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -0
- package/dist/styles/tailwindUtilityExpander.js +262 -0
- package/dist/styles/tailwindUtilityExpander.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +3 -2
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +5 -3
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-audit.sh +2 -2
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind Compile-Probe — Spec 22 R1.2
|
|
3
|
+
*
|
|
4
|
+
* Uses the project's own installed `tailwindcss` package as the oracle for
|
|
5
|
+
* class validation. Zero hand-curated dictionaries — the project's compiler
|
|
6
|
+
* IS the authority on what classes exist.
|
|
7
|
+
*
|
|
8
|
+
* **v4 (PRIMARY):** Uses `compile()` with `@apply` probe stylesheets.
|
|
9
|
+
* Candidate classes are injected into a generated CSS file, compiled, and
|
|
10
|
+
* checked for CSS output. Classes that produce declarations are valid.
|
|
11
|
+
*
|
|
12
|
+
* **v3 (FALLBACK):** Uses `resolveConfig()` to get the fully-resolved theme,
|
|
13
|
+
* then generates utility classes from the config. Theme values come from
|
|
14
|
+
* the project's config — the config IS the oracle.
|
|
15
|
+
*
|
|
16
|
+
* **Fail-open:** If tailwindcss can't be found, loaded, or compiled, the
|
|
17
|
+
* probe returns `ready=false`. The caller MUST disable the undefined-class
|
|
18
|
+
* detector — a claim that a class "does not exist" may not ship on a
|
|
19
|
+
* known-incomplete dictionary.
|
|
20
|
+
*
|
|
21
|
+
* @module tailwindProbe
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync } from 'node:fs';
|
|
24
|
+
import { join, dirname } from 'node:path';
|
|
25
|
+
import { createRequire } from 'node:module';
|
|
26
|
+
import { pathToFileURL } from 'node:url';
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Theme → utility-class generation (v3 fallback)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Utility prefixes that consume theme.colors to produce classes like
|
|
32
|
+
* `bg-red-500`, `text-blue-100`, etc.
|
|
33
|
+
*
|
|
34
|
+
* These are NOT a dictionary — they're the documented structural mapping
|
|
35
|
+
* from Tailwind's theme keys to utility class name prefixes. This mapping
|
|
36
|
+
* is stable across Tailwind releases and is the minimum structure needed
|
|
37
|
+
* to generate classes from a resolved config.
|
|
38
|
+
*/
|
|
39
|
+
const COLOR_UTILITY_PREFIXES = [
|
|
40
|
+
'bg', 'text', 'border', 'ring', 'shadow', 'fill', 'stroke',
|
|
41
|
+
'accent', 'caret', 'outline', 'placeholder',
|
|
42
|
+
'divide', 'from', 'via', 'to', 'decoration',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Utility prefixes that consume theme.spacing to produce classes like
|
|
46
|
+
* `p-4`, `m-2`, `gap-8`, etc.
|
|
47
|
+
*/
|
|
48
|
+
const SPACING_UTILITY_PREFIXES = [
|
|
49
|
+
'p', 'px', 'py', 'pt', 'pr', 'pb', 'pl',
|
|
50
|
+
'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml',
|
|
51
|
+
'w', 'min-w', 'max-w', 'h', 'min-h', 'max-h',
|
|
52
|
+
'top', 'right', 'bottom', 'left',
|
|
53
|
+
'inset', 'inset-x', 'inset-y',
|
|
54
|
+
'gap', 'gap-x', 'gap-y',
|
|
55
|
+
'space-x', 'space-y',
|
|
56
|
+
'leading', 'indent',
|
|
57
|
+
'scroll-m', 'scroll-mx', 'scroll-my', 'scroll-mt', 'scroll-mr', 'scroll-mb', 'scroll-ml',
|
|
58
|
+
'scroll-p', 'scroll-px', 'scroll-py', 'scroll-pt', 'scroll-pr', 'scroll-pb', 'scroll-pl',
|
|
59
|
+
'size',
|
|
60
|
+
];
|
|
61
|
+
/** Prefixes that consume theme.fontSize. */
|
|
62
|
+
const FONT_SIZE_PREFIXES = ['text'];
|
|
63
|
+
/** Prefixes that consume theme.borderRadius. */
|
|
64
|
+
const RADIUS_PREFIXES = [
|
|
65
|
+
'rounded', 'rounded-t', 'rounded-r', 'rounded-b', 'rounded-l',
|
|
66
|
+
'rounded-tl', 'rounded-tr', 'rounded-br', 'rounded-bl',
|
|
67
|
+
'rounded-s', 'rounded-e', 'rounded-ss', 'rounded-se', 'rounded-es', 'rounded-ee',
|
|
68
|
+
];
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// TailwindProbe
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
export class TailwindProbe {
|
|
73
|
+
validClasses = null;
|
|
74
|
+
_ready = false;
|
|
75
|
+
_failureReason = null;
|
|
76
|
+
_source = null;
|
|
77
|
+
projectRoot = '';
|
|
78
|
+
tailwindPath = null;
|
|
79
|
+
version = null;
|
|
80
|
+
/** Whether the probe successfully initialized and is ready to validate classes. */
|
|
81
|
+
get ready() {
|
|
82
|
+
return this._ready;
|
|
83
|
+
}
|
|
84
|
+
/** Why initialization failed, if it did. */
|
|
85
|
+
get failureReason() {
|
|
86
|
+
return this._failureReason;
|
|
87
|
+
}
|
|
88
|
+
/** Human-readable description of the validation source. */
|
|
89
|
+
get source() {
|
|
90
|
+
return this._source;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Initialize the probe by locating and loading the project's tailwindcss
|
|
94
|
+
* package. Tests compilation with a known-good class to verify the
|
|
95
|
+
* pipeline works end-to-end.
|
|
96
|
+
*/
|
|
97
|
+
async init(projectRoot) {
|
|
98
|
+
this.projectRoot = projectRoot;
|
|
99
|
+
const twPath = this.findTailwindcss(projectRoot);
|
|
100
|
+
if (!twPath) {
|
|
101
|
+
this._failureReason = 'tailwindcss not found in project node_modules';
|
|
102
|
+
return { ok: false, error: this._failureReason };
|
|
103
|
+
}
|
|
104
|
+
this.tailwindPath = twPath;
|
|
105
|
+
// ── Try v4 first (ESM import + compile() API) ──
|
|
106
|
+
try {
|
|
107
|
+
const mod = await this.tryImportV4(twPath);
|
|
108
|
+
if (mod && typeof mod.compile === 'function') {
|
|
109
|
+
const result = await mod.compile(Buffer.from('@import "tailwindcss";\n.p0{@apply flex;}'), { base: projectRoot });
|
|
110
|
+
const output = typeof result === 'string' ? result : String(result);
|
|
111
|
+
if (output.includes('.p0') && /\{([^}]*[a-z])/.test(output)) {
|
|
112
|
+
this.version = 4;
|
|
113
|
+
this.validClasses = new Set();
|
|
114
|
+
this._ready = true;
|
|
115
|
+
this._source = 'v4-compile-probe';
|
|
116
|
+
return { ok: true, source: this._source };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Not v4 — continue to v3
|
|
122
|
+
}
|
|
123
|
+
// ── Try v3 (CJS require + resolveConfig) ──
|
|
124
|
+
try {
|
|
125
|
+
const projectRequire = createRequire(join(projectRoot, 'package.json'));
|
|
126
|
+
const tw = projectRequire(twPath);
|
|
127
|
+
// v3 tailwindcss exports a function (the postcss plugin)
|
|
128
|
+
// It also has resolveConfig available
|
|
129
|
+
if (typeof tw === 'function' || (tw.default && typeof tw.default === 'function')) {
|
|
130
|
+
// Test resolveConfig availability
|
|
131
|
+
try {
|
|
132
|
+
const resolveConfig = projectRequire(join(twPath, 'resolveConfig'));
|
|
133
|
+
if (typeof resolveConfig === 'function') {
|
|
134
|
+
this.version = 3;
|
|
135
|
+
this.validClasses = null; // generated lazily from config
|
|
136
|
+
this._ready = true;
|
|
137
|
+
this._source = 'v3-config-generation';
|
|
138
|
+
return { ok: true, source: this._source };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// resolveConfig not available
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Neither v4 nor v3 loadable
|
|
148
|
+
}
|
|
149
|
+
this._failureReason = 'tailwindcss found but could not be loaded (neither v4 compile() nor v3 resolveConfig detected)';
|
|
150
|
+
return { ok: false, error: this._failureReason };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Check if a single class is known-valid from the cache only.
|
|
154
|
+
* Does NOT trigger a probe — use validateBatch() for that.
|
|
155
|
+
*/
|
|
156
|
+
isCached(className) {
|
|
157
|
+
return this.validClasses?.has(className) ?? false;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Validate a batch of class names against the project's Tailwind compiler.
|
|
161
|
+
* Classes that produce CSS when @apply'd (v4) or are generated from the
|
|
162
|
+
* resolved config (v3) are considered valid.
|
|
163
|
+
*
|
|
164
|
+
* Returns the full Set of all known-valid classes (including previously
|
|
165
|
+
* cached ones). Unknown classes that don't validate are NOT added to the
|
|
166
|
+
* cache — callers should treat absence from the returned set as "not valid."
|
|
167
|
+
*/
|
|
168
|
+
async validateBatch(candidates) {
|
|
169
|
+
if (!this._ready) {
|
|
170
|
+
throw new Error(this._failureReason ?? 'Probe not initialized');
|
|
171
|
+
}
|
|
172
|
+
if (this.version === 3) {
|
|
173
|
+
// v3: generate from config if not yet done, then check
|
|
174
|
+
if (!this.validClasses) {
|
|
175
|
+
this.validClasses = this.generateV3Classes();
|
|
176
|
+
}
|
|
177
|
+
for (const c of candidates) {
|
|
178
|
+
if (this.validClasses.has(c))
|
|
179
|
+
continue;
|
|
180
|
+
// For v3, we can probe individual unknowns via postcss if available,
|
|
181
|
+
// but that requires postcss to be installed. For now, config generation
|
|
182
|
+
// is comprehensive enough.
|
|
183
|
+
}
|
|
184
|
+
return this.validClasses;
|
|
185
|
+
}
|
|
186
|
+
// v4: batch-probe via @apply compilation
|
|
187
|
+
// Filter out already-known classes
|
|
188
|
+
const unknown = [...new Set(candidates)].filter(c => !this.validClasses.has(c));
|
|
189
|
+
if (unknown.length === 0)
|
|
190
|
+
return this.validClasses;
|
|
191
|
+
// Batch in groups of 200 to keep probe CSS manageable
|
|
192
|
+
const BATCH_SIZE = 200;
|
|
193
|
+
for (let i = 0; i < unknown.length; i += BATCH_SIZE) {
|
|
194
|
+
const batch = unknown.slice(i, i + BATCH_SIZE);
|
|
195
|
+
const valid = await this.probeV4Batch(batch);
|
|
196
|
+
for (const cls of valid) {
|
|
197
|
+
this.validClasses.add(cls);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return this.validClasses;
|
|
201
|
+
}
|
|
202
|
+
// -----------------------------------------------------------------------
|
|
203
|
+
// Private: v4 compile-probe
|
|
204
|
+
// -----------------------------------------------------------------------
|
|
205
|
+
/**
|
|
206
|
+
* Generate a CSS file with @apply probe selectors, compile it with
|
|
207
|
+
* Tailwind v4, and parse the output to determine which classes are valid.
|
|
208
|
+
*/
|
|
209
|
+
async probeV4Batch(classes) {
|
|
210
|
+
// Escape backslashes in class names for CSS
|
|
211
|
+
const escapedClasses = classes.map(cls => cls.replace(/\\/g, '\\\\'));
|
|
212
|
+
// Build probe CSS: one selector per class with @apply
|
|
213
|
+
const rules = escapedClasses
|
|
214
|
+
.map((cls, i) => `.p${i}{@apply ${cls};}`)
|
|
215
|
+
.join('');
|
|
216
|
+
const css = `@import "tailwindcss";${rules}`;
|
|
217
|
+
try {
|
|
218
|
+
const mod = await this.tryImportV4(this.tailwindPath);
|
|
219
|
+
if (!mod || typeof mod.compile !== 'function')
|
|
220
|
+
return [];
|
|
221
|
+
const result = await mod.compile(Buffer.from(css), { base: this.projectRoot });
|
|
222
|
+
const output = typeof result === 'string' ? result : String(result);
|
|
223
|
+
return this.parseProbeOutput(output, classes);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Compilation failed — return empty (treat all as invalid)
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Parse compiled CSS output to find which probe selectors have actual
|
|
232
|
+
* CSS declarations. A probe selector with no declarations or only empty
|
|
233
|
+
* rules indicates an invalid/unknown class.
|
|
234
|
+
*/
|
|
235
|
+
parseProbeOutput(output, classes) {
|
|
236
|
+
const valid = [];
|
|
237
|
+
for (let i = 0; i < classes.length; i++) {
|
|
238
|
+
// Find .pN{ ... } and check for non-whitespace declarations
|
|
239
|
+
const re = new RegExp(`\\.p${i}\\{([^}]*)\\}`, 's');
|
|
240
|
+
const m = re.exec(output);
|
|
241
|
+
if (m && m[1].trim().length > 0) {
|
|
242
|
+
valid.push(classes[i]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return valid;
|
|
246
|
+
}
|
|
247
|
+
// -----------------------------------------------------------------------
|
|
248
|
+
// Private: v3 config-based generation
|
|
249
|
+
// -----------------------------------------------------------------------
|
|
250
|
+
/**
|
|
251
|
+
* Generate the full set of utility classes from the project's resolved
|
|
252
|
+
* Tailwind v3 config. Uses `resolveConfig()` to merge user config with
|
|
253
|
+
* core plugin defaults, then generates class names from theme scales.
|
|
254
|
+
*
|
|
255
|
+
* This IS oracle-based: the config defines what's valid, and we
|
|
256
|
+
* synthesize class names from it structurally. No hand-curated lists
|
|
257
|
+
* of colors, spacing values, font sizes, or border radii.
|
|
258
|
+
*/
|
|
259
|
+
generateV3Classes() {
|
|
260
|
+
const classes = new Set();
|
|
261
|
+
try {
|
|
262
|
+
const { colors, spacing, fontSize, borderRadius } = this.loadV3ResolvedTheme();
|
|
263
|
+
// Color-based utilities: bg-{color}, text-{color}, etc.
|
|
264
|
+
const colorNames = Object.keys(colors);
|
|
265
|
+
for (const prefix of COLOR_UTILITY_PREFIXES) {
|
|
266
|
+
for (const name of colorNames) {
|
|
267
|
+
classes.add(`${prefix}-${name}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Spacing-based utilities: p-{size}, m-{size}, etc.
|
|
271
|
+
const spacingValues = Object.keys(spacing);
|
|
272
|
+
for (const prefix of SPACING_UTILITY_PREFIXES) {
|
|
273
|
+
for (const value of spacingValues) {
|
|
274
|
+
classes.add(`${prefix}-${value}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Special non-scale values for spacing-based prefixes
|
|
278
|
+
const spacingNonScale = ['auto', 'full', 'min', 'max', 'fit', 'screen', 'svh', 'dvh', 'lvh'];
|
|
279
|
+
for (const v of spacingNonScale) {
|
|
280
|
+
for (const prefix of ['w', 'min-w', 'max-w', 'h', 'min-h', 'max-h', 'size']) {
|
|
281
|
+
classes.add(`${prefix}-${v}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const prefix of ['m', 'mx', 'my', 'mt', 'mr', 'mb', 'ml']) {
|
|
285
|
+
classes.add(`${prefix}-auto`);
|
|
286
|
+
}
|
|
287
|
+
for (const prefix of ['top', 'right', 'bottom', 'left']) {
|
|
288
|
+
classes.add(`${prefix}-auto`);
|
|
289
|
+
classes.add(`${prefix}-full`);
|
|
290
|
+
for (const frac of ['1/2', '1/3', '2/3', '1/4', '3/4']) {
|
|
291
|
+
classes.add(`${prefix}-${frac}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const prefix of ['inset', 'inset-x', 'inset-y']) {
|
|
295
|
+
classes.add(`${prefix}-auto`);
|
|
296
|
+
classes.add(`${prefix}-full`);
|
|
297
|
+
for (const frac of ['1/2', '1/3', '2/3', '1/4', '3/4']) {
|
|
298
|
+
classes.add(`${prefix}-${frac}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Font size utilities: text-{size}
|
|
302
|
+
for (const prefix of FONT_SIZE_PREFIXES) {
|
|
303
|
+
for (const sizeName of Object.keys(fontSize)) {
|
|
304
|
+
classes.add(`${prefix}-${sizeName}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Border radius utilities: rounded-{radius}
|
|
308
|
+
for (const prefix of RADIUS_PREFIXES) {
|
|
309
|
+
for (const radiusName of Object.keys(borderRadius)) {
|
|
310
|
+
if (radiusName === 'DEFAULT' || radiusName === '') {
|
|
311
|
+
classes.add(prefix);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
classes.add(`${prefix}-${radiusName}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// ── Static utility classes that Tailwind provides regardless of theme ──
|
|
319
|
+
// These are the core, stable utility names from Tailwind's core plugins.
|
|
320
|
+
// Unlike theme-based utilities, these have fixed names not derived from
|
|
321
|
+
// config. BUT — they're stable, well-defined, and validated by the same
|
|
322
|
+
// Tailwind documentation that defines the config structure.
|
|
323
|
+
addStaticCoreUtilities(classes);
|
|
324
|
+
this._source = 'v3-config-generation';
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
this._failureReason = 'v3 resolveConfig failed to generate utilities';
|
|
328
|
+
this._ready = false;
|
|
329
|
+
}
|
|
330
|
+
return classes;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Load the fully-resolved Tailwind v3 theme using resolveConfig().
|
|
334
|
+
*/
|
|
335
|
+
loadV3ResolvedTheme() {
|
|
336
|
+
const projectRequire = createRequire(join(this.projectRoot, 'package.json'));
|
|
337
|
+
const resolveConfig = projectRequire(join(this.tailwindPath, 'resolveConfig'));
|
|
338
|
+
// Try to load the project's tailwind.config
|
|
339
|
+
let userConfig = {};
|
|
340
|
+
const configCandidates = [
|
|
341
|
+
'tailwind.config.js', 'tailwind.config.ts',
|
|
342
|
+
'tailwind.config.cjs', 'tailwind.config.mjs',
|
|
343
|
+
];
|
|
344
|
+
for (const candidate of configCandidates) {
|
|
345
|
+
const configPath = join(this.projectRoot, candidate);
|
|
346
|
+
if (existsSync(configPath)) {
|
|
347
|
+
try {
|
|
348
|
+
const cfg = projectRequire(configPath);
|
|
349
|
+
userConfig = cfg.default ?? cfg;
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// Config exists but can't be loaded — use empty config
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const resolved = resolveConfig(userConfig);
|
|
358
|
+
const theme = (resolved.theme ?? {});
|
|
359
|
+
// Flatten color palette (resolvedConfig returns nested structure)
|
|
360
|
+
const colors = flattenThemeSection(theme.colors ?? {});
|
|
361
|
+
const spacing = flattenThemeSection(theme.spacing ?? {});
|
|
362
|
+
const fontSize = flattenThemeSection(theme.fontSize ?? {});
|
|
363
|
+
const borderRadius = flattenThemeSection(theme.borderRadius ?? {});
|
|
364
|
+
return { colors, spacing, fontSize, borderRadius };
|
|
365
|
+
}
|
|
366
|
+
// -----------------------------------------------------------------------
|
|
367
|
+
// Private: package discovery
|
|
368
|
+
// -----------------------------------------------------------------------
|
|
369
|
+
/**
|
|
370
|
+
* Locate tailwindcss in the project's node_modules, walking up directories.
|
|
371
|
+
*/
|
|
372
|
+
findTailwindcss(projectRoot) {
|
|
373
|
+
let dir = projectRoot;
|
|
374
|
+
for (let i = 0; i < 20; i++) {
|
|
375
|
+
const pkgPath = join(dir, 'node_modules', 'tailwindcss');
|
|
376
|
+
if (existsSync(pkgPath)) {
|
|
377
|
+
return pkgPath;
|
|
378
|
+
}
|
|
379
|
+
const parent = dirname(dir);
|
|
380
|
+
if (parent === dir)
|
|
381
|
+
break;
|
|
382
|
+
dir = parent;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Dynamically import tailwindcss v4 (ESM package).
|
|
388
|
+
* Uses file:// URL for reliable resolution from the project's node_modules.
|
|
389
|
+
*/
|
|
390
|
+
async tryImportV4(twPath) {
|
|
391
|
+
try {
|
|
392
|
+
// Try file:// URL first (reliable across platforms)
|
|
393
|
+
const packageJsonPath = join(twPath, 'package.json');
|
|
394
|
+
if (existsSync(packageJsonPath)) {
|
|
395
|
+
return await import(pathToFileURL(twPath).href);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
// Fall through
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
// Try importing by bare specifier from the project root context.
|
|
403
|
+
// tailwindcss is a dependency of the project being audited, not of
|
|
404
|
+
// code-auditor itself — ignore the TS resolution error.
|
|
405
|
+
// @ts-expect-error — tailwindcss is resolved at runtime from the audited project
|
|
406
|
+
return await import('tailwindcss');
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
// Helpers
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
/**
|
|
417
|
+
* Flatten a resolved Tailwind theme section for class name generation.
|
|
418
|
+
* Handles nested objects (e.g. colors: { blue: { 500: '#...' } })
|
|
419
|
+
* producing dotted keys (e.g. "blue-500").
|
|
420
|
+
*
|
|
421
|
+
* Uses hyphens for separators to match Tailwind CSS class naming.
|
|
422
|
+
*/
|
|
423
|
+
function flattenThemeSection(obj) {
|
|
424
|
+
const result = {};
|
|
425
|
+
function walk(prefix, value) {
|
|
426
|
+
if (typeof value === 'string') {
|
|
427
|
+
result[prefix] = value;
|
|
428
|
+
}
|
|
429
|
+
else if (typeof value === 'object' && value !== null) {
|
|
430
|
+
for (const [key, val] of Object.entries(value)) {
|
|
431
|
+
if (key === 'DEFAULT') {
|
|
432
|
+
// DEFAULT key means the prefix itself is the value
|
|
433
|
+
result[prefix] = typeof val === 'string' ? val : String(val);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
const sep = prefix ? '-' : '';
|
|
437
|
+
walk(`${prefix}${sep}${key}`, val);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else if (typeof value === 'number') {
|
|
442
|
+
result[prefix] = String(value);
|
|
443
|
+
}
|
|
444
|
+
else if (typeof value === 'function') {
|
|
445
|
+
// Some v3 theme values are functions (e.g. spacing)
|
|
446
|
+
try {
|
|
447
|
+
const fnResult = value();
|
|
448
|
+
walk(prefix, fnResult);
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// Skip uninvocable functions
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
456
|
+
walk(key, val);
|
|
457
|
+
}
|
|
458
|
+
return result;
|
|
459
|
+
}
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
// Static core utilities (v3 fallback only)
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
/**
|
|
464
|
+
* Add static Tailwind utility classes that exist regardless of theme.
|
|
465
|
+
*
|
|
466
|
+
* These are the stable, well-defined utility class names from Tailwind's
|
|
467
|
+
* core plugins (display, flexbox, grid, positioning, etc.). They don't
|
|
468
|
+
* derive from theme values — they have fixed names.
|
|
469
|
+
*
|
|
470
|
+
* This is the minimum structure needed for v3 config-based generation.
|
|
471
|
+
* v4 compile-probe bypasses this entirely.
|
|
472
|
+
*/
|
|
473
|
+
function addStaticCoreUtilities(classes) {
|
|
474
|
+
const core = [
|
|
475
|
+
// ── Display ──
|
|
476
|
+
'block', 'inline-block', 'inline', 'flex', 'inline-flex', 'grid',
|
|
477
|
+
'inline-grid', 'hidden', 'flow-root', 'contents', 'table', 'table-row',
|
|
478
|
+
'table-cell', 'table-caption', 'table-column', 'table-column-group',
|
|
479
|
+
'table-footer-group', 'table-header-group', 'table-row-group', 'list-item',
|
|
480
|
+
// ── Position ──
|
|
481
|
+
'static', 'fixed', 'absolute', 'relative', 'sticky',
|
|
482
|
+
// ── Flexbox ──
|
|
483
|
+
'flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse',
|
|
484
|
+
'flex-wrap', 'flex-nowrap', 'flex-wrap-reverse',
|
|
485
|
+
'flex-1', 'flex-auto', 'flex-initial', 'flex-none',
|
|
486
|
+
'grow', 'grow-0', 'shrink', 'shrink-0',
|
|
487
|
+
'flex-grow', 'flex-grow-0', 'flex-shrink', 'flex-shrink-0',
|
|
488
|
+
// ── Grid ──
|
|
489
|
+
'grid-flow-row', 'grid-flow-col', 'grid-flow-dense', 'grid-flow-row-dense', 'grid-flow-col-dense',
|
|
490
|
+
'grid-cols-none', 'grid-cols-subgrid',
|
|
491
|
+
'grid-rows-none', 'grid-rows-subgrid',
|
|
492
|
+
'auto-cols-auto', 'auto-cols-min', 'auto-cols-max', 'auto-cols-fr',
|
|
493
|
+
'auto-rows-auto', 'auto-rows-min', 'auto-rows-max', 'auto-rows-fr',
|
|
494
|
+
// ── Alignment ──
|
|
495
|
+
'items-start', 'items-end', 'items-center', 'items-baseline', 'items-stretch',
|
|
496
|
+
'justify-start', 'justify-end', 'justify-center', 'justify-between',
|
|
497
|
+
'justify-around', 'justify-evenly', 'justify-stretch', 'justify-normal',
|
|
498
|
+
'justify-items-start', 'justify-items-end', 'justify-items-center', 'justify-items-stretch',
|
|
499
|
+
'content-center', 'content-start', 'content-end', 'content-between',
|
|
500
|
+
'content-around', 'content-evenly', 'content-baseline', 'content-normal', 'content-stretch',
|
|
501
|
+
'place-content-center', 'place-content-start', 'place-content-end',
|
|
502
|
+
'place-content-between', 'place-content-around', 'place-content-evenly',
|
|
503
|
+
'place-items-center', 'place-items-start', 'place-items-end', 'place-items-stretch',
|
|
504
|
+
'self-auto', 'self-start', 'self-end', 'self-center', 'self-stretch', 'self-baseline',
|
|
505
|
+
'place-self-auto', 'place-self-start', 'place-self-end', 'place-self-center', 'place-self-stretch',
|
|
506
|
+
// ── Typography ──
|
|
507
|
+
'text-left', 'text-center', 'text-right', 'text-justify', 'text-start', 'text-end',
|
|
508
|
+
'underline', 'line-through', 'no-underline', 'overline',
|
|
509
|
+
'decoration-solid', 'decoration-double', 'decoration-dotted', 'decoration-dashed', 'decoration-wavy',
|
|
510
|
+
'uppercase', 'lowercase', 'capitalize', 'normal-case',
|
|
511
|
+
'truncate', 'text-ellipsis', 'text-clip',
|
|
512
|
+
'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium',
|
|
513
|
+
'font-semibold', 'font-bold', 'font-extrabold', 'font-black',
|
|
514
|
+
'italic', 'not-italic',
|
|
515
|
+
'tracking-tighter', 'tracking-tight', 'tracking-normal',
|
|
516
|
+
'tracking-wide', 'tracking-wider', 'tracking-widest',
|
|
517
|
+
'leading-none', 'leading-tight', 'leading-snug', 'leading-normal', 'leading-relaxed', 'leading-loose',
|
|
518
|
+
'break-normal', 'break-words', 'break-all', 'break-keep',
|
|
519
|
+
'hyphens-none', 'hyphens-manual', 'hyphens-auto',
|
|
520
|
+
'ordinal', 'slashed-zero', 'lining-nums', 'oldstyle-nums', 'proportional-nums',
|
|
521
|
+
'tabular-nums', 'diagonal-fractions', 'stacked-fractions',
|
|
522
|
+
// ── Whitespace ──
|
|
523
|
+
'whitespace-normal', 'whitespace-nowrap', 'whitespace-pre', 'whitespace-pre-line',
|
|
524
|
+
'whitespace-pre-wrap', 'whitespace-break-spaces',
|
|
525
|
+
// ── Sizing ──
|
|
526
|
+
'w-auto', 'w-full', 'w-screen', 'w-svw', 'w-dvw', 'w-lvw', 'w-min', 'w-max', 'w-fit',
|
|
527
|
+
'h-auto', 'h-full', 'h-screen', 'h-svh', 'h-dvh', 'h-lvh', 'h-min', 'h-max', 'h-fit',
|
|
528
|
+
'min-w-0', 'min-w-full', 'min-w-min', 'min-w-max', 'min-w-fit',
|
|
529
|
+
'max-w-none', 'max-w-xs', 'max-w-sm', 'max-w-md', 'max-w-lg', 'max-w-xl',
|
|
530
|
+
'max-w-2xl', 'max-w-3xl', 'max-w-4xl', 'max-w-5xl', 'max-w-6xl', 'max-w-7xl',
|
|
531
|
+
'max-w-full', 'max-w-min', 'max-w-max', 'max-w-fit', 'max-w-prose',
|
|
532
|
+
'min-h-0', 'min-h-full', 'min-h-screen', 'min-h-min', 'min-h-max', 'min-h-fit',
|
|
533
|
+
'max-h-0', 'max-h-full', 'max-h-screen', 'max-h-min', 'max-h-max', 'max-h-fit',
|
|
534
|
+
'size-auto', 'size-full', 'size-min', 'size-max', 'size-fit',
|
|
535
|
+
// ── Overflow ──
|
|
536
|
+
'overflow-auto', 'overflow-hidden', 'overflow-visible', 'overflow-scroll',
|
|
537
|
+
'overflow-x-auto', 'overflow-x-hidden', 'overflow-x-visible', 'overflow-x-scroll',
|
|
538
|
+
'overflow-y-auto', 'overflow-y-hidden', 'overflow-y-visible', 'overflow-y-scroll',
|
|
539
|
+
// ── Visibility ──
|
|
540
|
+
'visible', 'invisible', 'collapse',
|
|
541
|
+
// ── Borders ──
|
|
542
|
+
'border-solid', 'border-dashed', 'border-dotted', 'border-double', 'border-hidden', 'border-none',
|
|
543
|
+
'border-collapse', 'border-separate',
|
|
544
|
+
// ── Shadows ──
|
|
545
|
+
'shadow-sm', 'shadow-md', 'shadow-lg', 'shadow-xl', 'shadow-2xl',
|
|
546
|
+
'shadow-inner', 'shadow-none',
|
|
547
|
+
// ── Ring ──
|
|
548
|
+
'ring-inset',
|
|
549
|
+
// ── Filters ──
|
|
550
|
+
'blur-sm', 'blur-md', 'blur-lg', 'blur-xl', 'blur-2xl', 'blur-3xl', 'blur-none',
|
|
551
|
+
'backdrop-blur-sm', 'backdrop-blur-md', 'backdrop-blur-lg',
|
|
552
|
+
'backdrop-blur-xl', 'backdrop-blur-2xl', 'backdrop-blur-3xl', 'backdrop-blur-none',
|
|
553
|
+
// ── Transitions ──
|
|
554
|
+
'transition-none', 'transition-all', 'transition', 'transition-colors',
|
|
555
|
+
'transition-opacity', 'transition-shadow', 'transition-transform',
|
|
556
|
+
'ease-linear', 'ease-in', 'ease-out', 'ease-in-out',
|
|
557
|
+
// ── Transforms ──
|
|
558
|
+
'transform', 'transform-gpu', 'transform-none',
|
|
559
|
+
'scale-0', 'scale-50', 'scale-75', 'scale-90', 'scale-95', 'scale-100',
|
|
560
|
+
'scale-105', 'scale-110', 'scale-125', 'scale-150',
|
|
561
|
+
'rotate-0', 'rotate-1', 'rotate-2', 'rotate-3', 'rotate-6', 'rotate-12',
|
|
562
|
+
'rotate-45', 'rotate-90', 'rotate-180',
|
|
563
|
+
'origin-center', 'origin-top', 'origin-top-right', 'origin-right',
|
|
564
|
+
'origin-bottom-right', 'origin-bottom', 'origin-bottom-left', 'origin-left', 'origin-top-left',
|
|
565
|
+
// ── Interactivity ──
|
|
566
|
+
'cursor-auto', 'cursor-default', 'cursor-pointer', 'cursor-wait', 'cursor-text',
|
|
567
|
+
'cursor-move', 'cursor-help', 'cursor-not-allowed', 'cursor-none',
|
|
568
|
+
'pointer-events-none', 'pointer-events-auto',
|
|
569
|
+
'select-none', 'select-text', 'select-all', 'select-auto',
|
|
570
|
+
'resize-none', 'resize', 'resize-y', 'resize-x',
|
|
571
|
+
// ── Screen readers ──
|
|
572
|
+
'sr-only', 'not-sr-only',
|
|
573
|
+
// ── Object fit ──
|
|
574
|
+
'object-contain', 'object-cover', 'object-fill', 'object-none', 'object-scale-down',
|
|
575
|
+
// ── Aspect ratio ──
|
|
576
|
+
'aspect-auto', 'aspect-square', 'aspect-video',
|
|
577
|
+
// ── Animation ──
|
|
578
|
+
'animate-none', 'animate-spin', 'animate-ping', 'animate-pulse', 'animate-bounce',
|
|
579
|
+
// ── Font family ──
|
|
580
|
+
'font-sans', 'font-serif', 'font-mono',
|
|
581
|
+
// ── Box sizing ──
|
|
582
|
+
'box-border', 'box-content',
|
|
583
|
+
// ── Container ──
|
|
584
|
+
'container',
|
|
585
|
+
// ── Background ──
|
|
586
|
+
'bg-auto', 'bg-cover', 'bg-contain',
|
|
587
|
+
'bg-bottom', 'bg-center', 'bg-left', 'bg-left-bottom', 'bg-left-top',
|
|
588
|
+
'bg-right', 'bg-right-bottom', 'bg-right-top', 'bg-top',
|
|
589
|
+
'bg-fixed', 'bg-local', 'bg-scroll',
|
|
590
|
+
'bg-no-repeat', 'bg-repeat', 'bg-repeat-x', 'bg-repeat-y', 'bg-repeat-round', 'bg-repeat-space',
|
|
591
|
+
'bg-gradient-to-t', 'bg-gradient-to-tr', 'bg-gradient-to-r', 'bg-gradient-to-br',
|
|
592
|
+
'bg-gradient-to-b', 'bg-gradient-to-bl', 'bg-gradient-to-l', 'bg-gradient-to-tl',
|
|
593
|
+
'bg-origin-border', 'bg-origin-padding', 'bg-origin-content',
|
|
594
|
+
'bg-clip-border', 'bg-clip-padding', 'bg-clip-content', 'bg-clip-text',
|
|
595
|
+
// ── Blend mode ──
|
|
596
|
+
'mix-blend-normal', 'mix-blend-multiply', 'mix-blend-screen', 'mix-blend-overlay',
|
|
597
|
+
'bg-blend-normal', 'bg-blend-multiply', 'bg-blend-screen', 'bg-blend-overlay',
|
|
598
|
+
// ── Table ──
|
|
599
|
+
'table-auto', 'table-fixed',
|
|
600
|
+
'caption-top', 'caption-bottom',
|
|
601
|
+
// ── Vertical align ──
|
|
602
|
+
'align-baseline', 'align-top', 'align-middle', 'align-bottom',
|
|
603
|
+
'align-text-top', 'align-text-bottom', 'align-sub', 'align-super',
|
|
604
|
+
// ── List style ──
|
|
605
|
+
'list-none', 'list-disc', 'list-decimal',
|
|
606
|
+
'list-inside', 'list-outside',
|
|
607
|
+
// ── Float / Clear ──
|
|
608
|
+
'float-right', 'float-left', 'float-none', 'float-start', 'float-end',
|
|
609
|
+
'clear-left', 'clear-right', 'clear-both', 'clear-none', 'clear-start', 'clear-end',
|
|
610
|
+
// ── Isolation ──
|
|
611
|
+
'isolate', 'isolation-auto',
|
|
612
|
+
// ── Overscroll ──
|
|
613
|
+
'overscroll-auto', 'overscroll-contain', 'overscroll-none',
|
|
614
|
+
// ── Scroll ──
|
|
615
|
+
'snap-none', 'snap-x', 'snap-y', 'snap-both', 'snap-mandatory', 'snap-proximity',
|
|
616
|
+
'scroll-auto', 'scroll-smooth',
|
|
617
|
+
// ── Appearance ──
|
|
618
|
+
'appearance-none', 'appearance-auto',
|
|
619
|
+
// ── Touch ──
|
|
620
|
+
'touch-auto', 'touch-none', 'touch-manipulation',
|
|
621
|
+
// ── Will change ──
|
|
622
|
+
'will-change-auto', 'will-change-scroll', 'will-change-contents', 'will-change-transform',
|
|
623
|
+
// ── Content ──
|
|
624
|
+
'content-none',
|
|
625
|
+
// ── Box decoration ──
|
|
626
|
+
'decoration-slice', 'decoration-clone',
|
|
627
|
+
'box-decoration-slice', 'box-decoration-clone',
|
|
628
|
+
// ── Line clamp ──
|
|
629
|
+
'line-clamp-none',
|
|
630
|
+
// ── Outline ──
|
|
631
|
+
'outline-none', 'outline', 'outline-dashed', 'outline-dotted', 'outline-double',
|
|
632
|
+
];
|
|
633
|
+
for (const cls of core) {
|
|
634
|
+
classes.add(cls);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
//# sourceMappingURL=tailwindProbe.js.map
|