code-auditor-mcp 3.4.2 → 3.4.4
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 +67 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +67 -34
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +9 -14
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +41 -56
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -2
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +81 -7
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +11 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +85 -29
- 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 +19 -8
- package/dist/auditRunner.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -20
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -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 +13 -1
- package/dist/conventions/conventionMiner.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/styles/styleExtractor.js +6 -3
- package/dist/styles/styleExtractor.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 +77 -53
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +146 -764
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
|
@@ -1,755 +1,139 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tailwind Utility Class Expander — Spec 22 R1
|
|
2
|
+
* Tailwind Utility Class Expander — Spec 22 R1.2 (compile-probe)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* UniversalStylesAnalyzer.detectUndefinedClasses.
|
|
4
|
+
* Validates Tailwind class names against the project's own installed
|
|
5
|
+
* `tailwindcss` package as the oracle. Zero hand-curated dictionaries —
|
|
6
|
+
* the project's compiler IS the authority on what classes exist.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
* When stripped, the underlying class should match a known utility or
|
|
19
|
-
* arbitrary-value pattern.
|
|
20
|
-
*/
|
|
21
|
-
const VARIANT_PREFIXES = [
|
|
22
|
-
// Responsive
|
|
23
|
-
'sm', 'md', 'lg', 'xl', '2xl',
|
|
24
|
-
// State
|
|
25
|
-
'hover', 'focus', 'active', 'disabled', 'visited',
|
|
26
|
-
'focus-visible', 'focus-within', 'focus-visible',
|
|
27
|
-
// Form
|
|
28
|
-
'checked', 'indeterminate', 'required', 'valid', 'invalid',
|
|
29
|
-
'in-range', 'out-of-range', 'placeholder-shown', 'autofill', 'read-only',
|
|
30
|
-
// Structure
|
|
31
|
-
'first', 'last', 'only', 'odd', 'even', 'first-of-type', 'last-of-type', 'only-of-type',
|
|
32
|
-
'empty',
|
|
33
|
-
// Mode
|
|
34
|
-
'dark', 'light',
|
|
35
|
-
// Group
|
|
36
|
-
'group-hover', 'group-focus', 'group-focus-visible', 'group-focus-within',
|
|
37
|
-
'group-active', 'group-disabled', 'group-visited', 'group-checked',
|
|
38
|
-
// Peer
|
|
39
|
-
'peer-hover', 'peer-focus', 'peer-focus-visible', 'peer-active',
|
|
40
|
-
'peer-disabled', 'peer-checked', 'peer-invalid', 'peer-required',
|
|
41
|
-
// Motion
|
|
42
|
-
'motion-safe', 'motion-reduce',
|
|
43
|
-
// Other
|
|
44
|
-
'before', 'after', 'first-letter', 'first-line',
|
|
45
|
-
'marker', 'selection', 'target', 'file', 'placeholder',
|
|
46
|
-
'rtl', 'ltr',
|
|
47
|
-
'open', 'closed',
|
|
48
|
-
'portrait', 'landscape',
|
|
49
|
-
'print',
|
|
50
|
-
// Aria
|
|
51
|
-
'aria-checked', 'aria-disabled',
|
|
52
|
-
'has-[:checked]', 'has-[:disabled]',
|
|
53
|
-
// Redundant but safe: already covered by individual state modifiers
|
|
54
|
-
];
|
|
55
|
-
// ─── Utility Prefixes (for arbitrary-value grammar) ─────────────────────────
|
|
56
|
-
/**
|
|
57
|
-
* Known Tailwind utility prefixes that can accept arbitrary values via
|
|
58
|
-
* the `prefix-[...]` syntax. Covers spacing, color, sizing, and other
|
|
59
|
-
* commonly-arbitrary-value'd families.
|
|
8
|
+
* **Primary (v4):** Uses `compile()` with `@apply` probe stylesheets.
|
|
9
|
+
* **Fallback (v3):** Uses `resolveConfig()` + theme generation.
|
|
10
|
+
* **Structural parsing:** Arbitrary values, variant prefixes, opacity
|
|
11
|
+
* modifiers, and negative utilities are parsed via structural regex
|
|
12
|
+
* patterns — never enumerated.
|
|
13
|
+
*
|
|
14
|
+
* **Fail-open rule:** If the probe can't initialize (no tailwindcss found,
|
|
15
|
+
* compile fails), `configFailed` is true. The caller MUST emit a visible
|
|
16
|
+
* warning and disable the undefined-class detector.
|
|
60
17
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'gap', 'gap-x', 'gap-y',
|
|
66
|
-
'w', 'min-w', 'max-w', 'h', 'min-h', 'max-h',
|
|
67
|
-
'top', 'right', 'bottom', 'left', 'inset',
|
|
68
|
-
'inset-x', 'inset-y',
|
|
69
|
-
'translate-x', 'translate-y', 'translate',
|
|
70
|
-
'skew-x', 'skew-y', 'skew',
|
|
71
|
-
'rotate',
|
|
72
|
-
'scale', 'scale-x', 'scale-y',
|
|
73
|
-
'space-x', 'space-y',
|
|
74
|
-
'leading', 'tracking', 'indent',
|
|
75
|
-
'scroll-m', 'scroll-mx', 'scroll-my', 'scroll-mt', 'scroll-mr', 'scroll-mb', 'scroll-ml',
|
|
76
|
-
'scroll-p', 'scroll-px', 'scroll-py', 'scroll-pt', 'scroll-pr', 'scroll-pb', 'scroll-pl',
|
|
77
|
-
// Color-based
|
|
78
|
-
'bg', 'text', 'border', 'ring', 'ring-offset', 'shadow',
|
|
79
|
-
'fill', 'stroke', 'outline', 'accent', 'caret',
|
|
80
|
-
'placeholder', 'divide',
|
|
81
|
-
'from', 'via', 'to', // gradient stops
|
|
82
|
-
'decoration',
|
|
83
|
-
// Sizing
|
|
84
|
-
'basis', 'grow', 'shrink',
|
|
85
|
-
'order',
|
|
86
|
-
'z', // z-index
|
|
87
|
-
// Border
|
|
88
|
-
'rounded', 'rounded-t', 'rounded-r', 'rounded-b', 'rounded-l',
|
|
89
|
-
'rounded-tl', 'rounded-tr', 'rounded-br', 'rounded-bl',
|
|
90
|
-
'rounded-s', 'rounded-e', 'rounded-ss', 'rounded-se', 'rounded-es', 'rounded-ee',
|
|
91
|
-
'border', 'border-t', 'border-r', 'border-b', 'border-l',
|
|
92
|
-
'border-x', 'border-y',
|
|
93
|
-
'outline',
|
|
94
|
-
'outline-offset',
|
|
95
|
-
// Text
|
|
96
|
-
'text', 'text-wrap',
|
|
97
|
-
'underline-offset',
|
|
98
|
-
// Opacity
|
|
99
|
-
'opacity',
|
|
100
|
-
'bg-opacity', 'text-opacity', 'border-opacity', 'ring-opacity',
|
|
101
|
-
'divide-opacity', 'placeholder-opacity',
|
|
102
|
-
// Shadow
|
|
103
|
-
'shadow',
|
|
104
|
-
// Duration / timing
|
|
105
|
-
'duration', 'delay', 'easing',
|
|
106
|
-
// Animation
|
|
107
|
-
'animate',
|
|
108
|
-
// Grid
|
|
109
|
-
'grid-cols', 'grid-rows', 'auto-cols', 'auto-rows',
|
|
110
|
-
'col', 'col-start', 'col-end', 'col-span',
|
|
111
|
-
'row', 'row-start', 'row-end', 'row-span',
|
|
112
|
-
// Columns
|
|
113
|
-
'columns',
|
|
114
|
-
// Filter
|
|
115
|
-
'blur', 'brightness', 'contrast', 'grayscale', 'hue-rotate',
|
|
116
|
-
'invert', 'saturate', 'sepia', 'drop-shadow',
|
|
117
|
-
'backdrop-blur', 'backdrop-brightness', 'backdrop-contrast',
|
|
118
|
-
'backdrop-grayscale', 'backdrop-hue-rotate', 'backdrop-invert',
|
|
119
|
-
'backdrop-opacity', 'backdrop-saturate', 'backdrop-sepia',
|
|
120
|
-
// Aspect ratio
|
|
121
|
-
'aspect',
|
|
122
|
-
// Break
|
|
123
|
-
'break',
|
|
124
|
-
// Container queries
|
|
125
|
-
'@',
|
|
126
|
-
]);
|
|
18
|
+
import { TailwindProbe } from './tailwindProbe.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Structural patterns (regex — NOT enumeration)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
127
22
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
23
|
+
* Regex matching Tailwind variant prefix syntax.
|
|
24
|
+
* Covers: responsive (sm/md/lg/xl/2xl), state (hover/focus/active/...),
|
|
25
|
+
* group/peer modifiers, dark/light, motion, aria, has, supports, etc.
|
|
26
|
+
*
|
|
27
|
+
* Pattern: `word` or `word-word` or `[...]` followed by `:`.
|
|
28
|
+
* This is a structural check — it does not enumerate valid variant names.
|
|
130
29
|
*/
|
|
131
|
-
const
|
|
132
|
-
// Margin — negative margins pull elements closer / overlap
|
|
133
|
-
'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml',
|
|
134
|
-
// Position — negative offsets move element beyond container edge
|
|
135
|
-
'top', 'right', 'bottom', 'left',
|
|
136
|
-
// Inset
|
|
137
|
-
'inset', 'inset-x', 'inset-y',
|
|
138
|
-
// Z-index
|
|
139
|
-
'z',
|
|
140
|
-
// Transform
|
|
141
|
-
'translate-x', 'translate-y',
|
|
142
|
-
'rotate',
|
|
143
|
-
'scale', 'scale-x', 'scale-y',
|
|
144
|
-
'skew-x', 'skew-y',
|
|
145
|
-
// Indent (negative text indent)
|
|
146
|
-
'indent',
|
|
147
|
-
]);
|
|
148
|
-
// ─── Static Utility Classes ─────────────────────────────────────────────────
|
|
30
|
+
const VARIANT_PREFIX_RE = /^(?:[a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*|\[.+?\]):/;
|
|
149
31
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
32
|
+
* Regex matching arbitrary-value syntax: `prefix-[...]`.
|
|
33
|
+
* Any word followed by `-[...]` is structurally a valid Tailwind
|
|
34
|
+
* arbitrary value. The brackets DO need to be balanced.
|
|
152
35
|
*/
|
|
153
|
-
|
|
154
|
-
return [
|
|
155
|
-
// Display
|
|
156
|
-
'block', 'inline-block', 'inline', 'flex', 'inline-flex', 'grid',
|
|
157
|
-
'inline-grid', 'hidden', 'flow-root', 'contents', 'table', 'table-row',
|
|
158
|
-
'table-cell', 'table-caption', 'table-column', 'table-column-group',
|
|
159
|
-
'table-footer-group', 'table-header-group', 'table-row-group', 'list-item',
|
|
160
|
-
// Position
|
|
161
|
-
'static', 'fixed', 'absolute', 'relative', 'sticky',
|
|
162
|
-
// Flex direction
|
|
163
|
-
'flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse',
|
|
164
|
-
'flex-wrap', 'flex-nowrap', 'flex-wrap-reverse',
|
|
165
|
-
'flex-1', 'flex-auto', 'flex-initial', 'flex-none',
|
|
166
|
-
'grow', 'grow-0', 'shrink', 'shrink-0',
|
|
167
|
-
'flex-grow', 'flex-grow-0', 'flex-shrink', 'flex-shrink-0',
|
|
168
|
-
'group',
|
|
169
|
-
// Grid
|
|
170
|
-
'grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4',
|
|
171
|
-
'grid-cols-5', 'grid-cols-6', 'grid-cols-7', 'grid-cols-8',
|
|
172
|
-
'grid-cols-9', 'grid-cols-10', 'grid-cols-11', 'grid-cols-12',
|
|
173
|
-
'grid-cols-none', 'grid-cols-subgrid',
|
|
174
|
-
'grid-rows-1', 'grid-rows-2', 'grid-rows-3', 'grid-rows-4',
|
|
175
|
-
'grid-rows-5', 'grid-rows-6', 'grid-rows-none', 'grid-rows-subgrid',
|
|
176
|
-
'grid-flow-row', 'grid-flow-col', 'grid-flow-dense', 'grid-flow-row-dense', 'grid-flow-col-dense',
|
|
177
|
-
'auto-cols-auto', 'auto-cols-min', 'auto-cols-max', 'auto-cols-fr',
|
|
178
|
-
'auto-rows-auto', 'auto-rows-min', 'auto-rows-max', 'auto-rows-fr',
|
|
179
|
-
// Alignment
|
|
180
|
-
'items-start', 'items-end', 'items-center', 'items-baseline', 'items-stretch',
|
|
181
|
-
'justify-start', 'justify-end', 'justify-center', 'justify-between',
|
|
182
|
-
'justify-around', 'justify-evenly', 'justify-stretch', 'justify-normal',
|
|
183
|
-
'justify-items-start', 'justify-items-end', 'justify-items-center', 'justify-items-stretch',
|
|
184
|
-
'content-center', 'content-start', 'content-end', 'content-between',
|
|
185
|
-
'content-around', 'content-evenly', 'content-baseline', 'content-normal', 'content-stretch',
|
|
186
|
-
'place-content-center', 'place-content-start', 'place-content-end',
|
|
187
|
-
'place-content-between', 'place-content-around', 'place-content-evenly',
|
|
188
|
-
'place-items-center', 'place-items-start', 'place-items-end', 'place-items-stretch',
|
|
189
|
-
'self-auto', 'self-start', 'self-end', 'self-center', 'self-stretch', 'self-baseline',
|
|
190
|
-
'place-self-auto', 'place-self-start', 'place-self-end', 'place-self-center', 'place-self-stretch',
|
|
191
|
-
// Text alignment
|
|
192
|
-
'text-left', 'text-center', 'text-right', 'text-justify', 'text-start', 'text-end',
|
|
193
|
-
// Text decoration
|
|
194
|
-
'underline', 'line-through', 'no-underline', 'overline',
|
|
195
|
-
'decoration-solid', 'decoration-double', 'decoration-dotted', 'decoration-dashed', 'decoration-wavy',
|
|
196
|
-
// Text transform
|
|
197
|
-
'uppercase', 'lowercase', 'capitalize', 'normal-case',
|
|
198
|
-
// Text overflow
|
|
199
|
-
'truncate', 'text-ellipsis', 'text-clip',
|
|
200
|
-
// Whitespace
|
|
201
|
-
'whitespace-normal', 'whitespace-nowrap', 'whitespace-pre', 'whitespace-pre-line', 'whitespace-pre-wrap',
|
|
202
|
-
'whitespace-break-spaces',
|
|
203
|
-
// Word break
|
|
204
|
-
'break-normal', 'break-words', 'break-all', 'break-keep', 'hyphens-none', 'hyphens-manual', 'hyphens-auto',
|
|
205
|
-
// Font weight
|
|
206
|
-
'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium',
|
|
207
|
-
'font-semibold', 'font-bold', 'font-extrabold', 'font-black',
|
|
208
|
-
// Font style
|
|
209
|
-
'italic', 'not-italic',
|
|
210
|
-
// Font variant
|
|
211
|
-
'ordinal', 'slashed-zero', 'lining-nums', 'oldstyle-nums', 'proportional-nums',
|
|
212
|
-
'tabular-nums', 'diagonal-fractions', 'stacked-fractions',
|
|
213
|
-
// Width
|
|
214
|
-
'w-auto', 'w-full', 'w-screen', 'w-svw', 'w-dvw', 'w-lvw', 'w-min', 'w-max', 'w-fit',
|
|
215
|
-
'w-0', 'w-px', 'w-0.5', 'w-1', 'w-2', 'w-3', 'w-4', 'w-5', 'w-6', 'w-7', 'w-8',
|
|
216
|
-
'w-9', 'w-10', 'w-11', 'w-12', 'w-14', 'w-16', 'w-20', 'w-24', 'w-28', 'w-32',
|
|
217
|
-
'w-36', 'w-40', 'w-44', 'w-48', 'w-52', 'w-56', 'w-60', 'w-64', 'w-72', 'w-80', 'w-96',
|
|
218
|
-
'w-1/2', 'w-1/3', 'w-2/3', 'w-1/4', 'w-2/4', 'w-3/4', 'w-1/5', 'w-2/5', 'w-3/5', 'w-4/5',
|
|
219
|
-
'w-1/6', 'w-2/6', 'w-3/6', 'w-4/6', 'w-5/6',
|
|
220
|
-
// Height
|
|
221
|
-
'h-auto', 'h-full', 'h-screen', 'h-svh', 'h-dvh', 'h-lvh', 'h-min', 'h-max', 'h-fit',
|
|
222
|
-
'h-0', 'h-px', 'h-0.5', 'h-1', 'h-2', 'h-3', 'h-4', 'h-5', 'h-6', 'h-7', 'h-8',
|
|
223
|
-
'h-9', 'h-10', 'h-11', 'h-12', 'h-14', 'h-16', 'h-20', 'h-24', 'h-28', 'h-32',
|
|
224
|
-
'h-36', 'h-40', 'h-44', 'h-48', 'h-52', 'h-56', 'h-60', 'h-64', 'h-72', 'h-80', 'h-96',
|
|
225
|
-
'h-1/2', 'h-1/3', 'h-2/3', 'h-1/4', 'h-2/4', 'h-3/4', 'h-1/5', 'h-2/5', 'h-3/5', 'h-4/5',
|
|
226
|
-
'h-1/6', 'h-2/6', 'h-3/6', 'h-4/6', 'h-5/6',
|
|
227
|
-
// Min/max sizing
|
|
228
|
-
'min-w-0', 'min-w-full', 'min-w-min', 'min-w-max', 'min-w-fit',
|
|
229
|
-
'max-w-0', 'max-w-none', 'max-w-xs', 'max-w-sm', 'max-w-md', 'max-w-lg', 'max-w-xl',
|
|
230
|
-
'max-w-2xl', 'max-w-3xl', 'max-w-4xl', 'max-w-5xl', 'max-w-6xl', 'max-w-7xl',
|
|
231
|
-
'max-w-full', 'max-w-min', 'max-w-max', 'max-w-fit', 'max-w-prose',
|
|
232
|
-
'max-w-screen-sm', 'max-w-screen-md', 'max-w-screen-lg', 'max-w-screen-xl', 'max-w-screen-2xl',
|
|
233
|
-
'min-h-0', 'min-h-full', 'min-h-screen', 'min-h-min', 'min-h-max', 'min-h-fit',
|
|
234
|
-
'max-h-0', 'max-h-full', 'max-h-screen', 'max-h-min', 'max-h-max', 'max-h-fit',
|
|
235
|
-
// Size (both w and h)
|
|
236
|
-
'size-auto', 'size-full', 'size-min', 'size-max', 'size-fit',
|
|
237
|
-
// Overflow
|
|
238
|
-
'overflow-auto', 'overflow-hidden', 'overflow-visible', 'overflow-scroll',
|
|
239
|
-
'overflow-x-auto', 'overflow-x-hidden', 'overflow-x-visible', 'overflow-x-scroll',
|
|
240
|
-
'overflow-y-auto', 'overflow-y-hidden', 'overflow-y-visible', 'overflow-y-scroll',
|
|
241
|
-
// Cursor
|
|
242
|
-
'cursor-auto', 'cursor-default', 'cursor-pointer', 'cursor-wait', 'cursor-text',
|
|
243
|
-
'cursor-move', 'cursor-help', 'cursor-not-allowed', 'cursor-none',
|
|
244
|
-
'cursor-context-menu', 'cursor-progress', 'cursor-cell', 'cursor-crosshair',
|
|
245
|
-
'cursor-vertical-text', 'cursor-alias', 'cursor-copy', 'cursor-grab', 'cursor-grabbing',
|
|
246
|
-
'cursor-no-drop', 'cursor-zoom-in', 'cursor-zoom-out',
|
|
247
|
-
// Visibility
|
|
248
|
-
'visible', 'invisible', 'collapse',
|
|
249
|
-
// Border
|
|
250
|
-
'border', 'border-0', 'border-2', 'border-4', 'border-8',
|
|
251
|
-
'border-t', 'border-r', 'border-b', 'border-l',
|
|
252
|
-
'border-t-0', 'border-r-0', 'border-b-0', 'border-l-0',
|
|
253
|
-
'border-x', 'border-y',
|
|
254
|
-
'border-solid', 'border-dashed', 'border-dotted', 'border-double', 'border-hidden', 'border-none',
|
|
255
|
-
// Shadow
|
|
256
|
-
'shadow', 'shadow-sm', 'shadow-md', 'shadow-lg', 'shadow-xl', 'shadow-2xl',
|
|
257
|
-
'shadow-inner', 'shadow-none',
|
|
258
|
-
// Ring
|
|
259
|
-
'ring', 'ring-0', 'ring-1', 'ring-2', 'ring-4', 'ring-8', 'ring-inset',
|
|
260
|
-
'ring-offset-0', 'ring-offset-1', 'ring-offset-2', 'ring-offset-4', 'ring-offset-8',
|
|
261
|
-
// Opacity
|
|
262
|
-
'opacity-0', 'opacity-5', 'opacity-10', 'opacity-20', 'opacity-25', 'opacity-30',
|
|
263
|
-
'opacity-40', 'opacity-50', 'opacity-60', 'opacity-70', 'opacity-75', 'opacity-80',
|
|
264
|
-
'opacity-90', 'opacity-95', 'opacity-100',
|
|
265
|
-
// Z-index
|
|
266
|
-
'z-0', 'z-10', 'z-20', 'z-30', 'z-40', 'z-50', 'z-auto',
|
|
267
|
-
// Object fit
|
|
268
|
-
'object-contain', 'object-cover', 'object-fill', 'object-none', 'object-scale-down',
|
|
269
|
-
// Object position
|
|
270
|
-
'object-bottom', 'object-center', 'object-left', 'object-left-bottom',
|
|
271
|
-
'object-left-top', 'object-right', 'object-right-bottom', 'object-right-top', 'object-top',
|
|
272
|
-
// Transition
|
|
273
|
-
'transition-none', 'transition-all', 'transition', 'transition-colors',
|
|
274
|
-
'transition-opacity', 'transition-shadow', 'transition-transform',
|
|
275
|
-
// Duration
|
|
276
|
-
'duration-0', 'duration-75', 'duration-100', 'duration-150', 'duration-200',
|
|
277
|
-
'duration-300', 'duration-500', 'duration-700', 'duration-1000',
|
|
278
|
-
// Delay
|
|
279
|
-
'delay-0', 'delay-75', 'delay-100', 'delay-150', 'delay-200', 'delay-300',
|
|
280
|
-
'delay-500', 'delay-700', 'delay-1000',
|
|
281
|
-
// Timing
|
|
282
|
-
'ease-linear', 'ease-in', 'ease-out', 'ease-in-out',
|
|
283
|
-
// Transform
|
|
284
|
-
'transform', 'transform-gpu', 'transform-none',
|
|
285
|
-
'scale-0', 'scale-50', 'scale-75', 'scale-90', 'scale-95', 'scale-100',
|
|
286
|
-
'scale-105', 'scale-110', 'scale-125', 'scale-150',
|
|
287
|
-
'scale-x-0', 'scale-x-50', 'scale-x-75', 'scale-x-90', 'scale-x-95', 'scale-x-100',
|
|
288
|
-
'scale-x-105', 'scale-x-110', 'scale-x-125', 'scale-x-150',
|
|
289
|
-
'scale-y-0', 'scale-y-50', 'scale-y-75', 'scale-y-90', 'scale-y-95', 'scale-y-100',
|
|
290
|
-
'scale-y-105', 'scale-y-110', 'scale-y-125', 'scale-y-150',
|
|
291
|
-
'rotate-0', 'rotate-1', 'rotate-2', 'rotate-3', 'rotate-6', 'rotate-12',
|
|
292
|
-
'rotate-45', 'rotate-90', 'rotate-180',
|
|
293
|
-
'origin-center', 'origin-top', 'origin-top-right', 'origin-right',
|
|
294
|
-
'origin-bottom-right', 'origin-bottom', 'origin-bottom-left', 'origin-left', 'origin-top-left',
|
|
295
|
-
// Box sizing
|
|
296
|
-
'box-border', 'box-content',
|
|
297
|
-
// Container
|
|
298
|
-
'container',
|
|
299
|
-
// Pointer events
|
|
300
|
-
'pointer-events-none', 'pointer-events-auto',
|
|
301
|
-
// Resize
|
|
302
|
-
'resize-none', 'resize', 'resize-y', 'resize-x',
|
|
303
|
-
// User select
|
|
304
|
-
'select-none', 'select-text', 'select-all', 'select-auto',
|
|
305
|
-
// Screen readers
|
|
306
|
-
'sr-only', 'not-sr-only',
|
|
307
|
-
// Background
|
|
308
|
-
'bg-auto', 'bg-cover', 'bg-contain',
|
|
309
|
-
'bg-bottom', 'bg-center', 'bg-left', 'bg-left-bottom', 'bg-left-top',
|
|
310
|
-
'bg-right', 'bg-right-bottom', 'bg-right-top', 'bg-top',
|
|
311
|
-
'bg-fixed', 'bg-local', 'bg-scroll',
|
|
312
|
-
'bg-no-repeat', 'bg-repeat', 'bg-repeat-x', 'bg-repeat-y', 'bg-repeat-round', 'bg-repeat-space',
|
|
313
|
-
'bg-none', 'bg-origin-border', 'bg-origin-padding', 'bg-origin-content',
|
|
314
|
-
'bg-clip-border', 'bg-clip-padding', 'bg-clip-content', 'bg-clip-text',
|
|
315
|
-
// Gradient
|
|
316
|
-
'bg-gradient-to-t', 'bg-gradient-to-tr', 'bg-gradient-to-r', 'bg-gradient-to-br',
|
|
317
|
-
'bg-gradient-to-b', 'bg-gradient-to-bl', 'bg-gradient-to-l', 'bg-gradient-to-tl',
|
|
318
|
-
'bg-none',
|
|
319
|
-
// List style
|
|
320
|
-
'list-none', 'list-disc', 'list-decimal', 'list-image-none',
|
|
321
|
-
'list-inside', 'list-outside',
|
|
322
|
-
// Table
|
|
323
|
-
'table-auto', 'table-fixed',
|
|
324
|
-
'border-collapse', 'border-separate',
|
|
325
|
-
'border-spacing-0', 'border-spacing-1', 'border-spacing-2',
|
|
326
|
-
'border-spacing-x-0', 'border-spacing-y-0',
|
|
327
|
-
'table-layout-auto', 'table-layout-fixed',
|
|
328
|
-
'caption-top', 'caption-bottom',
|
|
329
|
-
// Vertical align
|
|
330
|
-
'align-baseline', 'align-top', 'align-middle', 'align-bottom',
|
|
331
|
-
'align-text-top', 'align-text-bottom', 'align-sub', 'align-super',
|
|
332
|
-
// Aspect ratio
|
|
333
|
-
'aspect-auto', 'aspect-square', 'aspect-video',
|
|
334
|
-
// Columns
|
|
335
|
-
'columns-1', 'columns-2', 'columns-3', 'columns-4', 'columns-5', 'columns-6',
|
|
336
|
-
'columns-7', 'columns-8', 'columns-9', 'columns-10', 'columns-11', 'columns-12',
|
|
337
|
-
'columns-auto', 'columns-3xs', 'columns-2xs', 'columns-xs', 'columns-sm',
|
|
338
|
-
'columns-md', 'columns-lg', 'columns-xl', 'columns-2xl', 'columns-3xl',
|
|
339
|
-
'columns-4xl', 'columns-5xl', 'columns-6xl', 'columns-7xl',
|
|
340
|
-
'break-before-auto', 'break-before-avoid', 'break-before-all', 'break-before-avoid-page', 'break-before-page',
|
|
341
|
-
'break-before-left', 'break-before-right', 'break-before-column',
|
|
342
|
-
'break-after-auto', 'break-after-avoid', 'break-after-all', 'break-after-avoid-page', 'break-after-page',
|
|
343
|
-
'break-after-left', 'break-after-right', 'break-after-column',
|
|
344
|
-
'break-inside-auto', 'break-inside-avoid', 'break-inside-avoid-page', 'break-inside-avoid-column',
|
|
345
|
-
// Float
|
|
346
|
-
'float-right', 'float-left', 'float-none', 'float-start', 'float-end',
|
|
347
|
-
'clear-left', 'clear-right', 'clear-both', 'clear-none', 'clear-start', 'clear-end',
|
|
348
|
-
// Box decoration
|
|
349
|
-
'decoration-slice', 'decoration-clone',
|
|
350
|
-
'box-decoration-slice', 'box-decoration-clone',
|
|
351
|
-
// Isolation
|
|
352
|
-
'isolate', 'isolation-auto',
|
|
353
|
-
// Overscroll
|
|
354
|
-
'overscroll-auto', 'overscroll-contain', 'overscroll-none',
|
|
355
|
-
'overscroll-x-auto', 'overscroll-x-contain', 'overscroll-x-none',
|
|
356
|
-
'overscroll-y-auto', 'overscroll-y-contain', 'overscroll-y-none',
|
|
357
|
-
'overscroll-behavior-auto', 'overscroll-behavior-contain', 'overscroll-behavior-none',
|
|
358
|
-
// Scroll snap
|
|
359
|
-
'snap-none', 'snap-x', 'snap-y', 'snap-both', 'snap-mandatory', 'snap-proximity',
|
|
360
|
-
'snap-start', 'snap-end', 'snap-center', 'snap-align-none', 'snap-normal', 'snap-always',
|
|
361
|
-
// Scroll behavior
|
|
362
|
-
'scroll-auto', 'scroll-smooth',
|
|
363
|
-
// Appearance
|
|
364
|
-
'appearance-none', 'appearance-auto',
|
|
365
|
-
// Touch
|
|
366
|
-
'touch-auto', 'touch-none', 'touch-pan-x', 'touch-pan-left', 'touch-pan-right',
|
|
367
|
-
'touch-pan-y', 'touch-pan-up', 'touch-pan-down', 'touch-pinch-zoom',
|
|
368
|
-
'touch-manipulation',
|
|
369
|
-
// Will change
|
|
370
|
-
'will-change-auto', 'will-change-scroll', 'will-change-contents', 'will-change-transform',
|
|
371
|
-
// Content
|
|
372
|
-
'content-none',
|
|
373
|
-
// Blend mode
|
|
374
|
-
'mix-blend-normal', 'mix-blend-multiply', 'mix-blend-screen', 'mix-blend-overlay',
|
|
375
|
-
'mix-blend-darken', 'mix-blend-lighten', 'mix-blend-color-dodge', 'mix-blend-color-burn',
|
|
376
|
-
'mix-blend-hard-light', 'mix-blend-soft-light', 'mix-blend-difference',
|
|
377
|
-
'mix-blend-exclusion', 'mix-blend-hue', 'mix-blend-saturation', 'mix-blend-color', 'mix-blend-luminosity',
|
|
378
|
-
'mix-blend-plus-darker', 'mix-blend-plus-lighter',
|
|
379
|
-
'bg-blend-normal', 'bg-blend-multiply', 'bg-blend-screen', 'bg-blend-overlay',
|
|
380
|
-
'bg-blend-darken', 'bg-blend-lighten', 'bg-blend-color-dodge', 'bg-blend-color-burn',
|
|
381
|
-
// Gap
|
|
382
|
-
'gap-0', 'gap-px', 'gap-0.5', 'gap-1', 'gap-2', 'gap-3', 'gap-4', 'gap-5',
|
|
383
|
-
'gap-6', 'gap-7', 'gap-8', 'gap-9', 'gap-10', 'gap-11', 'gap-12',
|
|
384
|
-
'gap-14', 'gap-16', 'gap-20', 'gap-24', 'gap-28', 'gap-32', 'gap-36', 'gap-40',
|
|
385
|
-
'gap-44', 'gap-48', 'gap-52', 'gap-56', 'gap-60', 'gap-64', 'gap-72', 'gap-80', 'gap-96',
|
|
386
|
-
'gap-x-0', 'gap-y-0',
|
|
387
|
-
// Space
|
|
388
|
-
'space-x-0', 'space-y-0',
|
|
389
|
-
// Divide
|
|
390
|
-
'divide-x', 'divide-y',
|
|
391
|
-
'divide-x-0', 'divide-y-0',
|
|
392
|
-
'divide-solid', 'divide-dashed', 'divide-dotted', 'divide-double', 'divide-none',
|
|
393
|
-
'divide-x-reverse', 'divide-y-reverse',
|
|
394
|
-
// Ring
|
|
395
|
-
'ring-inset',
|
|
396
|
-
// Line clamp
|
|
397
|
-
'line-clamp-1', 'line-clamp-2', 'line-clamp-3', 'line-clamp-4', 'line-clamp-5', 'line-clamp-6', 'line-clamp-none',
|
|
398
|
-
// Animation
|
|
399
|
-
'animate-none', 'animate-spin', 'animate-ping', 'animate-pulse', 'animate-bounce',
|
|
400
|
-
// Font family
|
|
401
|
-
'font-sans', 'font-serif', 'font-mono',
|
|
402
|
-
// Letter spacing (tracking)
|
|
403
|
-
'tracking-tighter', 'tracking-tight', 'tracking-normal',
|
|
404
|
-
'tracking-wide', 'tracking-wider', 'tracking-widest',
|
|
405
|
-
// Backdrop filters
|
|
406
|
-
'backdrop-blur', 'backdrop-blur-sm', 'backdrop-blur-md', 'backdrop-blur-lg',
|
|
407
|
-
'backdrop-blur-xl', 'backdrop-blur-2xl', 'backdrop-blur-3xl', 'backdrop-blur-none',
|
|
408
|
-
'backdrop-opacity-0', 'backdrop-opacity-50', 'backdrop-opacity-100',
|
|
409
|
-
];
|
|
410
|
-
}
|
|
411
|
-
// ─── Generated Utility Classes (from theme scales) ──────────────────────────
|
|
36
|
+
const ARBITRARY_VALUE_RE = /^[a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*-\[.+\]$/;
|
|
412
37
|
/**
|
|
413
|
-
*
|
|
414
|
-
*
|
|
38
|
+
* Regex matching opacity-modifier syntax: `utility/opacity`.
|
|
39
|
+
* e.g., `bg-red-500/50`, `text-blue-100/75`.
|
|
40
|
+
* The part before the last `/` should be a valid utility,
|
|
41
|
+
* and the suffix should be a numeric opacity (0-100).
|
|
415
42
|
*/
|
|
416
|
-
|
|
417
|
-
const classes = [];
|
|
418
|
-
// Color prefixes — generate bg-{color}, text-{color}, etc.
|
|
419
|
-
const colorPrefixes = [
|
|
420
|
-
'bg', 'text', 'border', 'ring', 'shadow', 'fill', 'stroke',
|
|
421
|
-
'accent', 'caret', 'outline', 'placeholder', 'divide', 'from', 'via', 'to',
|
|
422
|
-
'decoration',
|
|
423
|
-
];
|
|
424
|
-
// Color names from the default Tailwind palette
|
|
425
|
-
const colorNames = [
|
|
426
|
-
'inherit', 'current', 'transparent',
|
|
427
|
-
'white', 'black',
|
|
428
|
-
// Slate
|
|
429
|
-
'slate-50', 'slate-100', 'slate-200', 'slate-300', 'slate-400', 'slate-500',
|
|
430
|
-
'slate-600', 'slate-700', 'slate-800', 'slate-900', 'slate-950',
|
|
431
|
-
// Gray
|
|
432
|
-
'gray-50', 'gray-100', 'gray-200', 'gray-300', 'gray-400', 'gray-500',
|
|
433
|
-
'gray-600', 'gray-700', 'gray-800', 'gray-900', 'gray-950',
|
|
434
|
-
// Zinc
|
|
435
|
-
'zinc-50', 'zinc-100', 'zinc-200', 'zinc-300', 'zinc-400', 'zinc-500',
|
|
436
|
-
'zinc-600', 'zinc-700', 'zinc-800', 'zinc-900', 'zinc-950',
|
|
437
|
-
// Neutral
|
|
438
|
-
'neutral-50', 'neutral-100', 'neutral-200', 'neutral-300', 'neutral-400', 'neutral-500',
|
|
439
|
-
'neutral-600', 'neutral-700', 'neutral-800', 'neutral-900', 'neutral-950',
|
|
440
|
-
// Stone
|
|
441
|
-
'stone-50', 'stone-100', 'stone-200', 'stone-300', 'stone-400', 'stone-500',
|
|
442
|
-
'stone-600', 'stone-700', 'stone-800', 'stone-900', 'stone-950',
|
|
443
|
-
// Red
|
|
444
|
-
'red-50', 'red-100', 'red-200', 'red-300', 'red-400', 'red-500',
|
|
445
|
-
'red-600', 'red-700', 'red-800', 'red-900', 'red-950',
|
|
446
|
-
// Orange
|
|
447
|
-
'orange-50', 'orange-100', 'orange-200', 'orange-300', 'orange-400', 'orange-500',
|
|
448
|
-
'orange-600', 'orange-700', 'orange-800', 'orange-900', 'orange-950',
|
|
449
|
-
// Amber
|
|
450
|
-
'amber-50', 'amber-100', 'amber-200', 'amber-300', 'amber-400', 'amber-500',
|
|
451
|
-
'amber-600', 'amber-700', 'amber-800', 'amber-900', 'amber-950',
|
|
452
|
-
// Yellow
|
|
453
|
-
'yellow-50', 'yellow-100', 'yellow-200', 'yellow-300', 'yellow-400', 'yellow-500',
|
|
454
|
-
'yellow-600', 'yellow-700', 'yellow-800', 'yellow-900', 'yellow-950',
|
|
455
|
-
// Lime
|
|
456
|
-
'lime-50', 'lime-100', 'lime-200', 'lime-300', 'lime-400', 'lime-500',
|
|
457
|
-
'lime-600', 'lime-700', 'lime-800', 'lime-900', 'lime-950',
|
|
458
|
-
// Green
|
|
459
|
-
'green-50', 'green-100', 'green-200', 'green-300', 'green-400', 'green-500',
|
|
460
|
-
'green-600', 'green-700', 'green-800', 'green-900', 'green-950',
|
|
461
|
-
// Emerald
|
|
462
|
-
'emerald-50', 'emerald-100', 'emerald-200', 'emerald-300', 'emerald-400', 'emerald-500',
|
|
463
|
-
'emerald-600', 'emerald-700', 'emerald-800', 'emerald-900', 'emerald-950',
|
|
464
|
-
// Teal
|
|
465
|
-
'teal-50', 'teal-100', 'teal-200', 'teal-300', 'teal-400', 'teal-500',
|
|
466
|
-
'teal-600', 'teal-700', 'teal-800', 'teal-900', 'teal-950',
|
|
467
|
-
// Cyan
|
|
468
|
-
'cyan-50', 'cyan-100', 'cyan-200', 'cyan-300', 'cyan-400', 'cyan-500',
|
|
469
|
-
'cyan-600', 'cyan-700', 'cyan-800', 'cyan-900', 'cyan-950',
|
|
470
|
-
// Sky
|
|
471
|
-
'sky-50', 'sky-100', 'sky-200', 'sky-300', 'sky-400', 'sky-500',
|
|
472
|
-
'sky-600', 'sky-700', 'sky-800', 'sky-900', 'sky-950',
|
|
473
|
-
// Blue
|
|
474
|
-
'blue-50', 'blue-100', 'blue-200', 'blue-300', 'blue-400', 'blue-500',
|
|
475
|
-
'blue-600', 'blue-700', 'blue-800', 'blue-900', 'blue-950',
|
|
476
|
-
// Indigo
|
|
477
|
-
'indigo-50', 'indigo-100', 'indigo-200', 'indigo-300', 'indigo-400', 'indigo-500',
|
|
478
|
-
'indigo-600', 'indigo-700', 'indigo-800', 'indigo-900', 'indigo-950',
|
|
479
|
-
// Violet
|
|
480
|
-
'violet-50', 'violet-100', 'violet-200', 'violet-300', 'violet-400', 'violet-500',
|
|
481
|
-
'violet-600', 'violet-700', 'violet-800', 'violet-900', 'violet-950',
|
|
482
|
-
// Purple
|
|
483
|
-
'purple-50', 'purple-100', 'purple-200', 'purple-300', 'purple-400', 'purple-500',
|
|
484
|
-
'purple-600', 'purple-700', 'purple-800', 'purple-900', 'purple-950',
|
|
485
|
-
// Fuchsia
|
|
486
|
-
'fuchsia-50', 'fuchsia-100', 'fuchsia-200', 'fuchsia-300', 'fuchsia-400', 'fuchsia-500',
|
|
487
|
-
'fuchsia-600', 'fuchsia-700', 'fuchsia-800', 'fuchsia-900', 'fuchsia-950',
|
|
488
|
-
// Pink
|
|
489
|
-
'pink-50', 'pink-100', 'pink-200', 'pink-300', 'pink-400', 'pink-500',
|
|
490
|
-
'pink-600', 'pink-700', 'pink-800', 'pink-900', 'pink-950',
|
|
491
|
-
// Rose
|
|
492
|
-
'rose-50', 'rose-100', 'rose-200', 'rose-300', 'rose-400', 'rose-500',
|
|
493
|
-
'rose-600', 'rose-700', 'rose-800', 'rose-900', 'rose-950',
|
|
494
|
-
];
|
|
495
|
-
// Special opacity variants for color prefixes that support it
|
|
496
|
-
const dividerColors = new Set(['divide', 'border', 'ring', 'placeholder', 'text']);
|
|
497
|
-
const opacityNumbers = ['0', '5', '10', '20', '25', '30', '40', '50', '60', '70', '75', '80', '90', '95', '100'];
|
|
498
|
-
for (const prefix of colorPrefixes) {
|
|
499
|
-
for (const colorName of colorNames) {
|
|
500
|
-
classes.push(`${prefix}-${colorName}`);
|
|
501
|
-
// Opacity modifiers for supported prefixes
|
|
502
|
-
if (dividerColors.has(prefix)) {
|
|
503
|
-
for (const opacity of opacityNumbers) {
|
|
504
|
-
classes.push(`${prefix}-${colorName}/${opacity}`);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
// Font size prefixes
|
|
510
|
-
const fontSizePrefixes = ['text'];
|
|
511
|
-
const fontSizeNames = [
|
|
512
|
-
'xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl', '8xl', '9xl',
|
|
513
|
-
];
|
|
514
|
-
for (const prefix of fontSizePrefixes) {
|
|
515
|
-
for (const sizeName of fontSizeNames) {
|
|
516
|
-
classes.push(`${prefix}-${sizeName}`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
// Border radius prefixes
|
|
520
|
-
const radiusPrefixes = [
|
|
521
|
-
'rounded', 'rounded-t', 'rounded-r', 'rounded-b', 'rounded-l',
|
|
522
|
-
'rounded-tl', 'rounded-tr', 'rounded-br', 'rounded-bl',
|
|
523
|
-
'rounded-s', 'rounded-e', 'rounded-ss', 'rounded-se', 'rounded-es', 'rounded-ee',
|
|
524
|
-
];
|
|
525
|
-
const radiusNames = ['none', 'sm', '', 'md', 'lg', 'xl', '2xl', '3xl', 'full'];
|
|
526
|
-
for (const prefix of radiusPrefixes) {
|
|
527
|
-
for (const radiusName of radiusNames) {
|
|
528
|
-
if (radiusName === '') {
|
|
529
|
-
classes.push(prefix); // "rounded" alone
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
classes.push(`${prefix}-${radiusName}`);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
// ── Spacing-scale utilities ─────────────────────────────────────────
|
|
537
|
-
// Padding, margin, position, inset, gap-x, gap-y, space-x, space-y,
|
|
538
|
-
// size, leading, indent, scroll-margin/padding — all accept the default
|
|
539
|
-
// spacing scale as values.
|
|
540
|
-
const spacingScaleValues = [
|
|
541
|
-
'0', 'px', '0.5', '1', '1.5', '2', '2.5', '3', '3.5', '4', '5', '6', '7',
|
|
542
|
-
'8', '9', '10', '11', '12', '14', '16', '20', '24', '28', '32', '36', '40',
|
|
543
|
-
'44', '48', '52', '56', '60', '64', '72', '80', '96',
|
|
544
|
-
];
|
|
545
|
-
// Spacing-based prefixes — utilities that accept spacing scale values
|
|
546
|
-
const spacingPrefixes = [
|
|
547
|
-
// Padding
|
|
548
|
-
'p', 'px', 'py', 'pt', 'pr', 'pb', 'pl',
|
|
549
|
-
// Margin
|
|
550
|
-
'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml',
|
|
551
|
-
// Position
|
|
552
|
-
'top', 'right', 'bottom', 'left',
|
|
553
|
-
// Inset
|
|
554
|
-
'inset', 'inset-x', 'inset-y',
|
|
555
|
-
// Gap (gap-{x,y} — full scale; gap base already in static)
|
|
556
|
-
'gap-x', 'gap-y',
|
|
557
|
-
// Space (space-{x,y} — full scale)
|
|
558
|
-
'space-x', 'space-y',
|
|
559
|
-
// Size (both width and height)
|
|
560
|
-
'size',
|
|
561
|
-
// Scroll margin / padding
|
|
562
|
-
'scroll-m', 'scroll-mx', 'scroll-my', 'scroll-mt', 'scroll-mr', 'scroll-mb', 'scroll-ml',
|
|
563
|
-
'scroll-p', 'scroll-px', 'scroll-py', 'scroll-pt', 'scroll-pr', 'scroll-pb', 'scroll-pl',
|
|
564
|
-
];
|
|
565
|
-
for (const prefix of spacingPrefixes) {
|
|
566
|
-
for (const value of spacingScaleValues) {
|
|
567
|
-
classes.push(`${prefix}-${value}`);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
// Special non-scale values for margin: auto
|
|
571
|
-
for (const prefix of ['m', 'mx', 'my', 'mt', 'mr', 'mb', 'ml']) {
|
|
572
|
-
classes.push(`${prefix}-auto`);
|
|
573
|
-
}
|
|
574
|
-
// Special non-scale values for position: auto, fractions, full
|
|
575
|
-
for (const prefix of ['top', 'right', 'bottom', 'left']) {
|
|
576
|
-
classes.push(`${prefix}-auto`);
|
|
577
|
-
classes.push(`${prefix}-full`);
|
|
578
|
-
for (const frac of ['1/2', '1/3', '2/3', '1/4', '3/4']) {
|
|
579
|
-
classes.push(`${prefix}-${frac}`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
// Special non-scale values for inset: auto, full, fractions
|
|
583
|
-
for (const prefix of ['inset', 'inset-x', 'inset-y']) {
|
|
584
|
-
classes.push(`${prefix}-auto`);
|
|
585
|
-
classes.push(`${prefix}-full`);
|
|
586
|
-
for (const frac of ['1/2', '1/3', '2/3', '1/4', '3/4']) {
|
|
587
|
-
classes.push(`${prefix}-${frac}`);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
// Leading (line-height) — spacing values + text keywords
|
|
591
|
-
for (const value of spacingScaleValues) {
|
|
592
|
-
classes.push(`leading-${value}`);
|
|
593
|
-
}
|
|
594
|
-
for (const kw of ['none', 'tight', 'snug', 'normal', 'relaxed', 'loose']) {
|
|
595
|
-
classes.push(`leading-${kw}`);
|
|
596
|
-
}
|
|
597
|
-
// Indent — spacing scale values
|
|
598
|
-
for (const value of spacingScaleValues) {
|
|
599
|
-
classes.push(`indent-${value}`);
|
|
600
|
-
}
|
|
601
|
-
// Border-width directional — only major widths for border-{side}-{width}
|
|
602
|
-
for (const side of ['t', 'r', 'b', 'l', 'x', 'y']) {
|
|
603
|
-
for (const w of ['0', '2', '4', '8']) {
|
|
604
|
-
classes.push(`border-${side}-${w}`);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
// Opacity modifiers for bg/text/shadow/ring (non-divider color prefixes)
|
|
608
|
-
// — divider colors (border/ring/placeholder/divide/text) already generated
|
|
609
|
-
const nonDividerOpacityPrefixes = ['bg', 'shadow'];
|
|
610
|
-
const opacityValues = ['0', '5', '10', '20', '25', '30', '40', '50', '60', '70', '75', '80', '90', '95', '100'];
|
|
611
|
-
for (const prefix of nonDividerOpacityPrefixes) {
|
|
612
|
-
for (const op of opacityValues) {
|
|
613
|
-
classes.push(`${prefix}-opacity-${op}`);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
// Translate — fractions and full
|
|
617
|
-
for (const prefix of ['translate-x', 'translate-y']) {
|
|
618
|
-
for (const frac of ['1/2', '1/3', '2/3', '1/4', '3/4', 'full']) {
|
|
619
|
-
classes.push(`${prefix}-${frac}`);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
// Grid column spans — col-span-{1..12}, col-start-{1..13}, col-end-{1..13}
|
|
623
|
-
for (let i = 1; i <= 12; i++)
|
|
624
|
-
classes.push(`col-span-${i}`);
|
|
625
|
-
for (let i = 1; i <= 13; i++) {
|
|
626
|
-
classes.push(`col-start-${i}`);
|
|
627
|
-
classes.push(`col-end-${i}`);
|
|
628
|
-
}
|
|
629
|
-
// Grid row spans — row-span-{1..6}, row-start-{1..7}, row-end-{1..7}
|
|
630
|
-
for (let i = 1; i <= 6; i++)
|
|
631
|
-
classes.push(`row-span-${i}`);
|
|
632
|
-
for (let i = 1; i <= 7; i++) {
|
|
633
|
-
classes.push(`row-start-${i}`);
|
|
634
|
-
classes.push(`row-end-${i}`);
|
|
635
|
-
}
|
|
636
|
-
// Outline utilities
|
|
637
|
-
classes.push('outline-none', 'outline', 'outline-dashed', 'outline-dotted', 'outline-double');
|
|
638
|
-
return classes;
|
|
639
|
-
}
|
|
640
|
-
// ─── Main Expander API ──────────────────────────────────────────────────────
|
|
641
|
-
/** Cached bundled utility classes. */
|
|
642
|
-
let _bundledClasses = null;
|
|
43
|
+
const OPACITY_MODIFIER_RE = /^(.+)\/(\d{1,3})$/;
|
|
643
44
|
/**
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
45
|
+
* Regex matching negative-utility syntax: `-utility`.
|
|
46
|
+
* Any utility prefixed with a leading `-` is structurally a negative
|
|
47
|
+
* Tailwind utility. We strip the `-` and check the positive utility.
|
|
647
48
|
*/
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
// Static utilities
|
|
653
|
-
for (const cls of getStaticUtilityClasses()) {
|
|
654
|
-
_bundledClasses.add(cls);
|
|
655
|
-
}
|
|
656
|
-
// Scale-generated utilities
|
|
657
|
-
for (const cls of generateScaleBasedClasses()) {
|
|
658
|
-
_bundledClasses.add(cls);
|
|
659
|
-
}
|
|
660
|
-
return _bundledClasses;
|
|
661
|
-
}
|
|
49
|
+
const NEGATIVE_UTILITY_RE = /^-(.+)$/;
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// TailwindUtilityExpander
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
662
53
|
/**
|
|
663
|
-
* Tailwind utility class expander.
|
|
54
|
+
* Tailwind utility class expander backed by compile-probe.
|
|
664
55
|
*
|
|
665
|
-
*
|
|
666
|
-
* 1.
|
|
667
|
-
* 2.
|
|
668
|
-
* 3.
|
|
669
|
-
* 4.
|
|
56
|
+
* Validation pipeline:
|
|
57
|
+
* 1. Probe cache (compile-probe or config-generation validated classes)
|
|
58
|
+
* 2. User-supplied custom classes
|
|
59
|
+
* 3. Opacity-modifier syntax (`utility/opacity`)
|
|
60
|
+
* 4. Arbitrary-value grammar (`prefix-[...]`)
|
|
61
|
+
* 5. Negative-utility syntax (`-utility`)
|
|
62
|
+
* 6. Variant prefix stripping + retry against all of the above
|
|
670
63
|
*
|
|
671
|
-
* Fail-open: if
|
|
672
|
-
*
|
|
673
|
-
* empty. The CALLER is responsible for emitting the visible warning and
|
|
64
|
+
* Fail-open: if the probe fails to initialize, configFailed is true.
|
|
65
|
+
* The caller is responsible for emitting a visible warning and
|
|
674
66
|
* potentially disabling the undefined-class detector.
|
|
675
67
|
*/
|
|
676
68
|
export class TailwindUtilityExpander {
|
|
677
|
-
|
|
678
|
-
|
|
69
|
+
probe = null;
|
|
70
|
+
customClasses = null;
|
|
679
71
|
_configFailed = false;
|
|
680
72
|
_configFailureReason = null;
|
|
681
73
|
/**
|
|
682
74
|
* Initialize the expander with optional project config.
|
|
683
|
-
* Call once
|
|
75
|
+
* Call once per audit. Async because it may compile-probe Tailwind.
|
|
684
76
|
*/
|
|
685
|
-
init(config = {}) {
|
|
686
|
-
this.
|
|
687
|
-
if (config.customClasses) {
|
|
688
|
-
this.projectClasses = new Set(config.customClasses);
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
77
|
+
async init(config = {}) {
|
|
78
|
+
this.customClasses = config.customClasses ?? null;
|
|
691
79
|
if (config.useProjectConfig && config.projectRoot) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
this.projectClasses = new Set();
|
|
696
|
-
// Generate utility classes from project-specific theme tokens
|
|
697
|
-
const colorPrefixes = ['bg', 'text', 'border', 'ring', 'shadow', 'fill', 'stroke', 'accent', 'caret'];
|
|
698
|
-
for (const colorName of Object.keys(result.tokens.colors)) {
|
|
699
|
-
for (const prefix of colorPrefixes) {
|
|
700
|
-
this.projectClasses.add(`${prefix}-${colorName}`);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
for (const spaceName of Object.keys(result.tokens.spacing)) {
|
|
704
|
-
for (const prefix of [
|
|
705
|
-
'p', 'px', 'py', 'pt', 'pr', 'pb', 'pl',
|
|
706
|
-
'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml',
|
|
707
|
-
'w', 'h', 'min-w', 'min-h', 'max-w', 'max-h',
|
|
708
|
-
'gap', 'gap-x', 'gap-y', 'space-x', 'space-y',
|
|
709
|
-
'top', 'right', 'bottom', 'left', 'inset',
|
|
710
|
-
'inset-x', 'inset-y',
|
|
711
|
-
'leading', 'indent',
|
|
712
|
-
]) {
|
|
713
|
-
this.projectClasses.add(`${prefix}-${spaceName}`);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
for (const sizeName of Object.keys(result.tokens.fontSize)) {
|
|
717
|
-
this.projectClasses.add(`text-${sizeName}`);
|
|
718
|
-
}
|
|
719
|
-
for (const radiusName of Object.keys(result.tokens.borderRadius)) {
|
|
720
|
-
this.projectClasses.add(`rounded-${radiusName}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
catch (err) {
|
|
80
|
+
this.probe = new TailwindProbe();
|
|
81
|
+
const result = await this.probe.init(config.projectRoot);
|
|
82
|
+
if (!result.ok) {
|
|
725
83
|
this._configFailed = true;
|
|
726
|
-
this._configFailureReason =
|
|
84
|
+
this._configFailureReason = result.error ?? 'Unknown probe init failure';
|
|
85
|
+
this.probe = null;
|
|
727
86
|
}
|
|
728
87
|
}
|
|
729
88
|
}
|
|
730
|
-
/** Did the
|
|
89
|
+
/** Did the probe initialization fail? */
|
|
731
90
|
get configFailed() {
|
|
732
91
|
return this._configFailed;
|
|
733
92
|
}
|
|
734
|
-
/** Why did
|
|
93
|
+
/** Why did probe initialization fail? */
|
|
735
94
|
get configFailureReason() {
|
|
736
95
|
return this._configFailureReason;
|
|
737
96
|
}
|
|
97
|
+
/** Whether the probe is ready (initialized successfully). */
|
|
98
|
+
get probeReady() {
|
|
99
|
+
return this.probe?.ready ?? false;
|
|
100
|
+
}
|
|
101
|
+
/** Human-readable source of validation data. */
|
|
102
|
+
get probeSource() {
|
|
103
|
+
return this.probe?.source ?? null;
|
|
104
|
+
}
|
|
738
105
|
/**
|
|
739
|
-
*
|
|
740
|
-
*
|
|
106
|
+
* Validate a batch of unknown class names against the compile-probe.
|
|
107
|
+
* Call this BEFORE resolve() to pre-populate the validation cache.
|
|
108
|
+
*
|
|
109
|
+
* Classes that pass validation are cached internally; resolve() will
|
|
110
|
+
* find them on subsequent calls.
|
|
111
|
+
*
|
|
112
|
+
* Returns the full set of all validated classes (including previously
|
|
113
|
+
* cached ones).
|
|
114
|
+
*/
|
|
115
|
+
async validateBatch(candidates) {
|
|
116
|
+
if (!this.probe?.ready) {
|
|
117
|
+
// No probe — use custom classes only
|
|
118
|
+
return this.customClasses ?? new Set();
|
|
119
|
+
}
|
|
120
|
+
return this.probe.validateBatch(candidates);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolve a single class name against the validation pipeline.
|
|
124
|
+
* Synchronous — only checks cached data + structural patterns.
|
|
125
|
+
* Call validateBatch() first to populate the cache for unknown classes.
|
|
741
126
|
*/
|
|
742
127
|
resolve(className) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
return { valid: true, tier: 'bundled' };
|
|
128
|
+
// 1. Check probe cache
|
|
129
|
+
if (this.probe?.isCached(className)) {
|
|
130
|
+
return { valid: true, tier: 'probe' };
|
|
747
131
|
}
|
|
748
|
-
// 2. Check
|
|
749
|
-
if (this.
|
|
750
|
-
return { valid: true, tier: '
|
|
132
|
+
// 2. Check custom (user-supplied) classes
|
|
133
|
+
if (this.customClasses?.has(className)) {
|
|
134
|
+
return { valid: true, tier: 'probe' };
|
|
751
135
|
}
|
|
752
|
-
// 3. Check opacity-modifier syntax: {
|
|
136
|
+
// 3. Check opacity-modifier syntax: {utility}/{opacity}
|
|
753
137
|
if (className.includes('/')) {
|
|
754
138
|
const result = this.checkOpacityModifier(className);
|
|
755
139
|
if (result)
|
|
@@ -759,79 +143,70 @@ export class TailwindUtilityExpander {
|
|
|
759
143
|
if (this.matchesArbitraryValue(className)) {
|
|
760
144
|
return { valid: true, tier: 'arbitrary-value' };
|
|
761
145
|
}
|
|
762
|
-
// 5. Check negative-
|
|
146
|
+
// 5. Check negative-utility syntax: -{utility}
|
|
763
147
|
const negResult = this.checkNegativeUtility(className);
|
|
764
148
|
if (negResult)
|
|
765
149
|
return negResult;
|
|
766
150
|
// 6. Strip variant prefix and retry
|
|
767
151
|
const stripped = this.stripVariantPrefix(className);
|
|
768
152
|
if (stripped !== className) {
|
|
769
|
-
return this.resolve(stripped);
|
|
153
|
+
return this.resolve(stripped);
|
|
770
154
|
}
|
|
771
155
|
return { valid: false, tier: 'none' };
|
|
772
156
|
}
|
|
773
157
|
/**
|
|
774
158
|
* Check whether a class matches the arbitrary-value grammar:
|
|
775
|
-
* `{
|
|
159
|
+
* `{prefix}-[...]` — any word prefix followed by `-[...]`.
|
|
160
|
+
*
|
|
161
|
+
* Structural check only — does not enumerate valid prefixes.
|
|
162
|
+
* The Tailwind compiler is the authority on whether a prefix
|
|
163
|
+
* actually accepts arbitrary values; our job is to not false-positive
|
|
164
|
+
* on obviously-arbitrary syntax.
|
|
776
165
|
*/
|
|
777
166
|
matchesArbitraryValue(className) {
|
|
778
|
-
|
|
779
|
-
if (bracketIdx <= 0)
|
|
780
|
-
return false;
|
|
781
|
-
// Extract the prefix before the [
|
|
782
|
-
const prefix = className.substring(0, bracketIdx);
|
|
783
|
-
// Handle prefix-[value] where prefix ends with '-'
|
|
784
|
-
if (prefix.endsWith('-')) {
|
|
785
|
-
const normalizedPrefix = prefix.slice(0, -1);
|
|
786
|
-
return ARBITRARY_VALUE_PREFIXES.has(normalizedPrefix);
|
|
787
|
-
}
|
|
788
|
-
return false;
|
|
167
|
+
return ARBITRARY_VALUE_RE.test(className);
|
|
789
168
|
}
|
|
790
169
|
/**
|
|
791
|
-
* Check
|
|
792
|
-
* e.g., `bg-red-500/50` → `bg-red-500` must be a valid utility AND
|
|
793
|
-
* `50` must be a valid opacity value.
|
|
170
|
+
* Check opacity-modifier syntax: `{utility}/{opacity}`.
|
|
794
171
|
*
|
|
795
|
-
*
|
|
796
|
-
* (e.g. `w-1/2`)
|
|
797
|
-
*
|
|
172
|
+
* Split on the LAST `/` to avoid confusing fractional widths
|
|
173
|
+
* (e.g. `w-1/2`). The prefix must be a known utility, suffix
|
|
174
|
+
* must be a numeric opacity (0-100).
|
|
798
175
|
*/
|
|
799
176
|
checkOpacityModifier(className) {
|
|
800
|
-
const
|
|
801
|
-
if (
|
|
802
|
-
return null;
|
|
803
|
-
const prefix = className.substring(0, lastSlash);
|
|
804
|
-
const suffix = className.substring(lastSlash + 1);
|
|
805
|
-
// Suffix must be a numeric opacity value (0-100)
|
|
806
|
-
if (!/^\d{1,3}$/.test(suffix))
|
|
177
|
+
const m = OPACITY_MODIFIER_RE.exec(className);
|
|
178
|
+
if (!m)
|
|
807
179
|
return null;
|
|
180
|
+
const prefix = m[1];
|
|
181
|
+
const suffix = m[2];
|
|
808
182
|
const opacity = parseInt(suffix, 10);
|
|
809
183
|
if (opacity < 0 || opacity > 100)
|
|
810
184
|
return null;
|
|
811
|
-
// Prefix must be a known utility
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
185
|
+
// Prefix must be a known utility — check against probe cache
|
|
186
|
+
// and custom classes (structural check isn't enough here because
|
|
187
|
+
// "foo-bar/50" should not match unless "foo-bar" is valid).
|
|
188
|
+
if (this.probe?.isCached(prefix) || this.customClasses?.has(prefix)) {
|
|
189
|
+
return { valid: true, tier: 'probe' };
|
|
190
|
+
}
|
|
191
|
+
// Also check if the prefix itself resolves (handles variant-prefixed
|
|
192
|
+
// utilities with opacity: hover:bg-red-500/50)
|
|
193
|
+
const prefixResolution = this.resolve(prefix);
|
|
194
|
+
if (prefixResolution.valid) {
|
|
195
|
+
return { valid: true, tier: prefixResolution.tier };
|
|
815
196
|
}
|
|
816
197
|
return null;
|
|
817
198
|
}
|
|
818
199
|
/**
|
|
819
|
-
* Check
|
|
820
|
-
* e.g., `-mt-1`, `-top-2`, `-translate-x-1/2`, `-z-10`.
|
|
200
|
+
* Check negative-utility syntax: `-{utility}`.
|
|
821
201
|
*
|
|
822
|
-
*
|
|
823
|
-
* utility
|
|
202
|
+
* Structural check — strips the leading `-` and verifies the positive
|
|
203
|
+
* utility itself is valid. No hand-curated allowed-prefixes list.
|
|
824
204
|
*/
|
|
825
205
|
checkNegativeUtility(className) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const positive = className.slice(1); // strip leading '-'
|
|
829
|
-
// Determine the prefix of the positive utility
|
|
830
|
-
// e.g., "top-2" → prefix="top", "translate-x-1/2" → prefix="translate-x"
|
|
831
|
-
const lastDash = positive.lastIndexOf('-');
|
|
832
|
-
const prefix = lastDash > 0 ? positive.substring(0, lastDash) : positive;
|
|
833
|
-
if (!NEGATIVE_ALLOWED_PREFIXES.has(prefix))
|
|
206
|
+
const m = NEGATIVE_UTILITY_RE.exec(className);
|
|
207
|
+
if (!m)
|
|
834
208
|
return null;
|
|
209
|
+
const positive = m[1];
|
|
835
210
|
// Recurse: is the positive utility itself valid?
|
|
836
211
|
const result = this.resolve(positive);
|
|
837
212
|
if (result.valid) {
|
|
@@ -841,14 +216,19 @@ export class TailwindUtilityExpander {
|
|
|
841
216
|
}
|
|
842
217
|
/**
|
|
843
218
|
* Strip a Tailwind variant prefix from a class name.
|
|
219
|
+
* Uses structural regex — matches any `word:` or `word-word:` prefix.
|
|
220
|
+
*
|
|
844
221
|
* E.g., "hover:bg-blue-500" → "bg-blue-500"
|
|
222
|
+
* "md:w-full" → "w-full"
|
|
223
|
+
* "group-hover:text-red" → "text-red"
|
|
224
|
+
* "[&_>_a]:text-blue" → "text-blue" (arbitrary variant)
|
|
225
|
+
*
|
|
845
226
|
* Returns the original if no variant prefix is matched.
|
|
846
227
|
*/
|
|
847
228
|
stripVariantPrefix(className) {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
}
|
|
229
|
+
const m = VARIANT_PREFIX_RE.exec(className);
|
|
230
|
+
if (m) {
|
|
231
|
+
return className.slice(m[0].length);
|
|
852
232
|
}
|
|
853
233
|
return className;
|
|
854
234
|
}
|
|
@@ -856,17 +236,19 @@ export class TailwindUtilityExpander {
|
|
|
856
236
|
* Check if a class has a variant prefix at all.
|
|
857
237
|
*/
|
|
858
238
|
hasVariantPrefix(className) {
|
|
859
|
-
return
|
|
239
|
+
return VARIANT_PREFIX_RE.test(className);
|
|
860
240
|
}
|
|
861
241
|
/** Reset state (useful for testing). */
|
|
862
242
|
reset() {
|
|
863
|
-
this.
|
|
864
|
-
this.
|
|
243
|
+
this.probe = null;
|
|
244
|
+
this.customClasses = null;
|
|
865
245
|
this._configFailed = false;
|
|
866
246
|
this._configFailureReason = null;
|
|
867
247
|
}
|
|
868
248
|
}
|
|
869
|
-
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Singleton
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
870
252
|
let _instance = null;
|
|
871
253
|
export function getTailwindExpander() {
|
|
872
254
|
if (!_instance) {
|