code-auditor-mcp 3.4.1 → 3.4.2

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +1 -1
  3. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  4. package/dist/analyzers/orm/drizzleAdapter.d.ts +15 -0
  5. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
  6. package/dist/analyzers/orm/drizzleAdapter.js +35 -2
  7. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
  8. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  9. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +27 -8
  10. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  11. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +12 -0
  12. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  13. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +18 -1
  14. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  15. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +9 -0
  16. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  17. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +39 -0
  18. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  19. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +17 -0
  20. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  21. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +98 -33
  22. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  23. package/dist/auditRunner.d.ts.map +1 -1
  24. package/dist/auditRunner.js +23 -14
  25. package/dist/auditRunner.js.map +1 -1
  26. package/dist/codeIndexDB.d.ts +1 -0
  27. package/dist/codeIndexDB.d.ts.map +1 -1
  28. package/dist/codeIndexDB.js +18 -4
  29. package/dist/codeIndexDB.js.map +1 -1
  30. package/dist/componentScanner.d.ts.map +1 -1
  31. package/dist/componentScanner.js +4 -3
  32. package/dist/componentScanner.js.map +1 -1
  33. package/dist/config/defaults.d.ts +1 -0
  34. package/dist/config/defaults.d.ts.map +1 -1
  35. package/dist/config/defaults.js +9 -2
  36. package/dist/config/defaults.js.map +1 -1
  37. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  38. package/dist/conventions/conventionMiner.js +144 -47
  39. package/dist/conventions/conventionMiner.js.map +1 -1
  40. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  41. package/dist/languages/UniversalAnalyzer.js +2 -0
  42. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  43. package/dist/languages/adapterBridge.d.ts +7 -0
  44. package/dist/languages/adapterBridge.d.ts.map +1 -1
  45. package/dist/languages/adapterBridge.js +9 -2
  46. package/dist/languages/adapterBridge.js.map +1 -1
  47. package/dist/languages/tree-sitter/converter.d.ts +9 -1
  48. package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
  49. package/dist/languages/tree-sitter/converter.js +13 -6
  50. package/dist/languages/tree-sitter/converter.js.map +1 -1
  51. package/dist/scripts/runBench.d.ts.map +1 -1
  52. package/dist/scripts/runBench.js +16 -4
  53. package/dist/scripts/runBench.js.map +1 -1
  54. package/dist/styles/styleIndexer.js +9 -3
  55. package/dist/styles/styleIndexer.js.map +1 -1
  56. package/dist/styles/tailwindUtilityExpander.d.ts +116 -0
  57. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -0
  58. package/dist/styles/tailwindUtilityExpander.js +880 -0
  59. package/dist/styles/tailwindUtilityExpander.js.map +1 -0
  60. package/dist/types.d.ts +4 -0
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js.map +1 -1
  63. package/dist/utils/astParser.d.ts.map +1 -1
  64. package/dist/utils/astParser.js +3 -2
  65. package/dist/utils/astParser.js.map +1 -1
  66. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  67. package/dist/utils/dependencyExtractor.js +5 -3
  68. package/dist/utils/dependencyExtractor.js.map +1 -1
  69. package/package.json +1 -1
  70. package/plugin/scripts/hook-audit.sh +2 -2
@@ -0,0 +1,880 @@
1
+ /**
2
+ * Tailwind Utility Class Expander — Spec 22 R1
3
+ *
4
+ * Generates the full set of valid Tailwind utility classes from default theme
5
+ * scales plus arbitrary-value grammar, variant prefix stripping, and project
6
+ * config resolution. Replaces the hand-curated ~30-class Set previously in
7
+ * UniversalStylesAnalyzer.detectUndefinedClasses.
8
+ *
9
+ * Fail-open rule (per Spec 22 R1.3): if project Tailwind config resolution
10
+ * fails, the undefined-class detector disables for that project with one
11
+ * visible warning naming the failure. A claim that a class "does not exist"
12
+ * may not ship on a known-incomplete dictionary.
13
+ */
14
+ import { loadTailwindConfig } from './tailwindConfigLoader.js';
15
+ // ─── Variant Prefixes ──────────────────────────────────────────────────────
16
+ /**
17
+ * Tailwind variant prefixes that modify utilities.
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.
60
+ */
61
+ const ARBITRARY_VALUE_PREFIXES = new Set([
62
+ // Spacing-based
63
+ 'p', 'px', 'py', 'pt', 'pr', 'pb', 'pl',
64
+ 'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml',
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
+ ]);
127
+ /**
128
+ * Prefixes that allow a negative sign to be prepended to the utility.
129
+ * e.g., -mt-1 (negative margin-top), -top-2 (negative position).
130
+ */
131
+ const NEGATIVE_ALLOWED_PREFIXES = new Set([
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 ─────────────────────────────────────────────────
149
+ /**
150
+ * Standard Tailwind utility classes that don't depend on a theme scale.
151
+ * These are the fixed-name classes: display, flex, grid, alignment, etc.
152
+ */
153
+ function getStaticUtilityClasses() {
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) ──────────────────────────
412
+ /**
413
+ * Generate all valid Tailwind utility classes by combining known prefixes with
414
+ * default theme scales + common string values for non-scale prefixes.
415
+ */
416
+ function generateScaleBasedClasses() {
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;
643
+ /**
644
+ * Get the full set of bundled Tailwind utility classes.
645
+ * Generated from the default theme scales plus static utilities.
646
+ * Cached after first call — callers get the same Set instance.
647
+ */
648
+ export function getBundledUtilityClasses() {
649
+ if (_bundledClasses)
650
+ return _bundledClasses;
651
+ _bundledClasses = new Set();
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
+ }
662
+ /**
663
+ * Tailwind utility class expander.
664
+ *
665
+ * Validates a class name against the full expansion path:
666
+ * 1. Bundled default theme dictionary
667
+ * 2. Project config custom classes (if resolved)
668
+ * 3. Arbitrary-value grammar (`prefix-[...]`)
669
+ * 4. Variant prefix stripping + retry against all of the above
670
+ *
671
+ * Fail-open: if project config resolution is requested but fails, the
672
+ * expander enters fail-open mode where project-config tier is silently
673
+ * empty. The CALLER is responsible for emitting the visible warning and
674
+ * potentially disabling the undefined-class detector.
675
+ */
676
+ export class TailwindUtilityExpander {
677
+ bundled = null;
678
+ projectClasses = null;
679
+ _configFailed = false;
680
+ _configFailureReason = null;
681
+ /**
682
+ * Initialize the expander with optional project config.
683
+ * Call once at the start of a project audit.
684
+ */
685
+ init(config = {}) {
686
+ this.bundled = getBundledUtilityClasses();
687
+ if (config.customClasses) {
688
+ this.projectClasses = new Set(config.customClasses);
689
+ return;
690
+ }
691
+ if (config.useProjectConfig && config.projectRoot) {
692
+ try {
693
+ const result = loadTailwindConfig(config.projectRoot);
694
+ if (result && result.tokens) {
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) {
725
+ this._configFailed = true;
726
+ this._configFailureReason = err instanceof Error ? err.message : String(err);
727
+ }
728
+ }
729
+ }
730
+ /** Did the project config resolution fail? */
731
+ get configFailed() {
732
+ return this._configFailed;
733
+ }
734
+ /** Why did project config resolution fail? */
735
+ get configFailureReason() {
736
+ return this._configFailureReason;
737
+ }
738
+ /**
739
+ * Resolve a single class name against the expansion path.
740
+ * Returns the highest-priority matching tier, or 'none'.
741
+ */
742
+ resolve(className) {
743
+ const bundled = this.bundled ?? getBundledUtilityClasses();
744
+ // 1. Check raw class against bundled dictionary
745
+ if (bundled.has(className)) {
746
+ return { valid: true, tier: 'bundled' };
747
+ }
748
+ // 2. Check raw class against project config
749
+ if (this.projectClasses?.has(className)) {
750
+ return { valid: true, tier: 'project-config' };
751
+ }
752
+ // 3. Check opacity-modifier syntax: {color-utility}/{opacity}
753
+ if (className.includes('/')) {
754
+ const result = this.checkOpacityModifier(className);
755
+ if (result)
756
+ return result;
757
+ }
758
+ // 4. Check arbitrary-value grammar: prefix-[...]
759
+ if (this.matchesArbitraryValue(className)) {
760
+ return { valid: true, tier: 'arbitrary-value' };
761
+ }
762
+ // 5. Check negative-value utility: -{prefix}-{value}
763
+ const negResult = this.checkNegativeUtility(className);
764
+ if (negResult)
765
+ return negResult;
766
+ // 6. Strip variant prefix and retry
767
+ const stripped = this.stripVariantPrefix(className);
768
+ if (stripped !== className) {
769
+ return this.resolve(stripped); // Recurse once with the stripped class
770
+ }
771
+ return { valid: false, tier: 'none' };
772
+ }
773
+ /**
774
+ * Check whether a class matches the arbitrary-value grammar:
775
+ * `{known-prefix}-[...]`
776
+ */
777
+ matchesArbitraryValue(className) {
778
+ const bracketIdx = className.indexOf('[');
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;
789
+ }
790
+ /**
791
+ * Check the opacity-modifier syntax: `{utility}/{opacity}`.
792
+ * e.g., `bg-red-500/50` → `bg-red-500` must be a valid utility AND
793
+ * `50` must be a valid opacity value.
794
+ *
795
+ * We split on the LAST `/` to avoid confusing fractional widths
796
+ * (e.g. `w-1/2`) — those are in the dictionary directly and won't
797
+ * reach this check.
798
+ */
799
+ checkOpacityModifier(className) {
800
+ const lastSlash = className.lastIndexOf('/');
801
+ if (lastSlash <= 0)
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))
807
+ return null;
808
+ const opacity = parseInt(suffix, 10);
809
+ if (opacity < 0 || opacity > 100)
810
+ return null;
811
+ // Prefix must be a known utility
812
+ const bundled = this.bundled ?? getBundledUtilityClasses();
813
+ if (bundled.has(prefix) || this.projectClasses?.has(prefix)) {
814
+ return { valid: true, tier: 'project-config' };
815
+ }
816
+ return null;
817
+ }
818
+ /**
819
+ * Check a negative utility: `-{prefix}-{value}`.
820
+ * e.g., `-mt-1`, `-top-2`, `-translate-x-1/2`, `-z-10`.
821
+ *
822
+ * Strips the leading `-` and checks whether the remainder is a valid
823
+ * utility whose prefix is in the negative-allowed set.
824
+ */
825
+ checkNegativeUtility(className) {
826
+ if (!className.startsWith('-'))
827
+ return null;
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))
834
+ return null;
835
+ // Recurse: is the positive utility itself valid?
836
+ const result = this.resolve(positive);
837
+ if (result.valid) {
838
+ return { valid: true, tier: result.tier };
839
+ }
840
+ return null;
841
+ }
842
+ /**
843
+ * Strip a Tailwind variant prefix from a class name.
844
+ * E.g., "hover:bg-blue-500" → "bg-blue-500"
845
+ * Returns the original if no variant prefix is matched.
846
+ */
847
+ stripVariantPrefix(className) {
848
+ for (const prefix of VARIANT_PREFIXES) {
849
+ if (className.startsWith(prefix + ':')) {
850
+ return className.slice(prefix.length + 1);
851
+ }
852
+ }
853
+ return className;
854
+ }
855
+ /**
856
+ * Check if a class has a variant prefix at all.
857
+ */
858
+ hasVariantPrefix(className) {
859
+ return className.includes(':') && this.stripVariantPrefix(className) !== className;
860
+ }
861
+ /** Reset state (useful for testing). */
862
+ reset() {
863
+ this.bundled = null;
864
+ this.projectClasses = null;
865
+ this._configFailed = false;
866
+ this._configFailureReason = null;
867
+ }
868
+ }
869
+ /** Singleton instance for the analyzer layer. */
870
+ let _instance = null;
871
+ export function getTailwindExpander() {
872
+ if (!_instance) {
873
+ _instance = new TailwindUtilityExpander();
874
+ }
875
+ return _instance;
876
+ }
877
+ export function resetTailwindExpander() {
878
+ _instance = null;
879
+ }
880
+ //# sourceMappingURL=tailwindUtilityExpander.js.map