tailwind-typescript-plugin 1.4.0-beta.3 → 1.4.0-beta.5
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/CHANGELOG.md +16 -0
- package/README.md +6 -6
- package/lib/extractors/TailwindVariantsExtractor.d.ts.map +1 -1
- package/lib/extractors/TailwindVariantsExtractor.js +0 -4
- package/lib/extractors/TailwindVariantsExtractor.js.map +1 -1
- package/lib/infrastructure/TailwindConflictDetector.d.ts +28 -18
- package/lib/infrastructure/TailwindConflictDetector.d.ts.map +1 -1
- package/lib/infrastructure/TailwindConflictDetector.js +216 -486
- package/lib/infrastructure/TailwindConflictDetector.js.map +1 -1
- package/lib/infrastructure/TailwindValidator.d.ts +6 -0
- package/lib/infrastructure/TailwindValidator.d.ts.map +1 -1
- package/lib/infrastructure/TailwindValidator.js +19 -0
- package/lib/infrastructure/TailwindValidator.js.map +1 -1
- package/lib/plugin/TailwindTypescriptPlugin.d.ts +5 -0
- package/lib/plugin/TailwindTypescriptPlugin.d.ts.map +1 -1
- package/lib/plugin/TailwindTypescriptPlugin.js +12 -1
- package/lib/plugin/TailwindTypescriptPlugin.js.map +1 -1
- package/lib/services/ConflictClassDetection.spec.js +24 -19
- package/lib/services/ConflictClassDetection.spec.js.map +1 -1
- package/lib/services/DuplicateClassDetection.spec.js +41 -60
- package/lib/services/DuplicateClassDetection.spec.js.map +1 -1
- package/lib/services/ValidationService.d.ts +2 -1
- package/lib/services/ValidationService.d.ts.map +1 -1
- package/lib/services/ValidationService.js +10 -7
- package/lib/services/ValidationService.js.map +1 -1
- package/package.json +3 -1
- package/lib/extractors/StringLiteralExtractor.d.ts +0 -12
- package/lib/extractors/StringLiteralExtractor.d.ts.map +0 -1
- package/lib/extractors/StringLiteralExtractor.js +0 -21
- package/lib/extractors/StringLiteralExtractor.js.map +0 -1
- package/lib/services/ClassNameExtractionService.original.d.ts +0 -20
- package/lib/services/ClassNameExtractionService.original.d.ts.map +0 -1
- package/lib/services/ClassNameExtractionService.original.js +0 -48
- package/lib/services/ClassNameExtractionService.original.js.map +0 -1
|
@@ -1,429 +1,76 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.TailwindConflictDetector = void 0;
|
|
4
|
-
|
|
5
|
-
* Conflict groups define sets of Tailwind classes that affect the same CSS property.
|
|
6
|
-
* When multiple classes from the same group are used together, they conflict.
|
|
7
|
-
*/
|
|
8
|
-
const CONFLICT_GROUPS = {
|
|
9
|
-
// Text alignment
|
|
10
|
-
'text-align': [
|
|
11
|
-
'text-left',
|
|
12
|
-
'text-center',
|
|
13
|
-
'text-right',
|
|
14
|
-
'text-justify',
|
|
15
|
-
'text-start',
|
|
16
|
-
'text-end'
|
|
17
|
-
],
|
|
18
|
-
// Display
|
|
19
|
-
display: [
|
|
20
|
-
'block',
|
|
21
|
-
'inline-block',
|
|
22
|
-
'inline',
|
|
23
|
-
'flex',
|
|
24
|
-
'inline-flex',
|
|
25
|
-
'table',
|
|
26
|
-
'inline-table',
|
|
27
|
-
'table-caption',
|
|
28
|
-
'table-cell',
|
|
29
|
-
'table-column',
|
|
30
|
-
'table-column-group',
|
|
31
|
-
'table-footer-group',
|
|
32
|
-
'table-header-group',
|
|
33
|
-
'table-row-group',
|
|
34
|
-
'table-row',
|
|
35
|
-
'flow-root',
|
|
36
|
-
'grid',
|
|
37
|
-
'inline-grid',
|
|
38
|
-
'contents',
|
|
39
|
-
'list-item',
|
|
40
|
-
'hidden'
|
|
41
|
-
],
|
|
42
|
-
// Position
|
|
43
|
-
position: ['static', 'fixed', 'absolute', 'relative', 'sticky'],
|
|
44
|
-
// Visibility
|
|
45
|
-
visibility: ['visible', 'invisible', 'collapse'],
|
|
46
|
-
// Flex direction
|
|
47
|
-
'flex-direction': ['flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse'],
|
|
48
|
-
// Flex wrap
|
|
49
|
-
'flex-wrap': ['flex-wrap', 'flex-wrap-reverse', 'flex-nowrap'],
|
|
50
|
-
// Justify content
|
|
51
|
-
'justify-content': [
|
|
52
|
-
'justify-normal',
|
|
53
|
-
'justify-start',
|
|
54
|
-
'justify-end',
|
|
55
|
-
'justify-center',
|
|
56
|
-
'justify-between',
|
|
57
|
-
'justify-around',
|
|
58
|
-
'justify-evenly',
|
|
59
|
-
'justify-stretch'
|
|
60
|
-
],
|
|
61
|
-
// Justify items
|
|
62
|
-
'justify-items': [
|
|
63
|
-
'justify-items-start',
|
|
64
|
-
'justify-items-end',
|
|
65
|
-
'justify-items-center',
|
|
66
|
-
'justify-items-stretch'
|
|
67
|
-
],
|
|
68
|
-
// Justify self
|
|
69
|
-
'justify-self': [
|
|
70
|
-
'justify-self-auto',
|
|
71
|
-
'justify-self-start',
|
|
72
|
-
'justify-self-end',
|
|
73
|
-
'justify-self-center',
|
|
74
|
-
'justify-self-stretch'
|
|
75
|
-
],
|
|
76
|
-
// Align content
|
|
77
|
-
'align-content': [
|
|
78
|
-
'content-normal',
|
|
79
|
-
'content-center',
|
|
80
|
-
'content-start',
|
|
81
|
-
'content-end',
|
|
82
|
-
'content-between',
|
|
83
|
-
'content-around',
|
|
84
|
-
'content-evenly',
|
|
85
|
-
'content-baseline',
|
|
86
|
-
'content-stretch'
|
|
87
|
-
],
|
|
88
|
-
// Align items
|
|
89
|
-
'align-items': ['items-start', 'items-end', 'items-center', 'items-baseline', 'items-stretch'],
|
|
90
|
-
// Align self
|
|
91
|
-
'align-self': [
|
|
92
|
-
'self-auto',
|
|
93
|
-
'self-start',
|
|
94
|
-
'self-end',
|
|
95
|
-
'self-center',
|
|
96
|
-
'self-stretch',
|
|
97
|
-
'self-baseline'
|
|
98
|
-
],
|
|
99
|
-
// Place content
|
|
100
|
-
'place-content': [
|
|
101
|
-
'place-content-center',
|
|
102
|
-
'place-content-start',
|
|
103
|
-
'place-content-end',
|
|
104
|
-
'place-content-between',
|
|
105
|
-
'place-content-around',
|
|
106
|
-
'place-content-evenly',
|
|
107
|
-
'place-content-baseline',
|
|
108
|
-
'place-content-stretch'
|
|
109
|
-
],
|
|
110
|
-
// Place items
|
|
111
|
-
'place-items': [
|
|
112
|
-
'place-items-start',
|
|
113
|
-
'place-items-end',
|
|
114
|
-
'place-items-center',
|
|
115
|
-
'place-items-baseline',
|
|
116
|
-
'place-items-stretch'
|
|
117
|
-
],
|
|
118
|
-
// Place self
|
|
119
|
-
'place-self': [
|
|
120
|
-
'place-self-auto',
|
|
121
|
-
'place-self-start',
|
|
122
|
-
'place-self-end',
|
|
123
|
-
'place-self-center',
|
|
124
|
-
'place-self-stretch'
|
|
125
|
-
],
|
|
126
|
-
// Float
|
|
127
|
-
float: ['float-start', 'float-end', 'float-right', 'float-left', 'float-none'],
|
|
128
|
-
// Clear
|
|
129
|
-
clear: ['clear-start', 'clear-end', 'clear-left', 'clear-right', 'clear-both', 'clear-none'],
|
|
130
|
-
// Object fit
|
|
131
|
-
'object-fit': [
|
|
132
|
-
'object-contain',
|
|
133
|
-
'object-cover',
|
|
134
|
-
'object-fill',
|
|
135
|
-
'object-none',
|
|
136
|
-
'object-scale-down'
|
|
137
|
-
],
|
|
138
|
-
// Overflow
|
|
139
|
-
overflow: [
|
|
140
|
-
'overflow-auto',
|
|
141
|
-
'overflow-hidden',
|
|
142
|
-
'overflow-clip',
|
|
143
|
-
'overflow-visible',
|
|
144
|
-
'overflow-scroll'
|
|
145
|
-
],
|
|
146
|
-
'overflow-x': [
|
|
147
|
-
'overflow-x-auto',
|
|
148
|
-
'overflow-x-hidden',
|
|
149
|
-
'overflow-x-clip',
|
|
150
|
-
'overflow-x-visible',
|
|
151
|
-
'overflow-x-scroll'
|
|
152
|
-
],
|
|
153
|
-
'overflow-y': [
|
|
154
|
-
'overflow-y-auto',
|
|
155
|
-
'overflow-y-hidden',
|
|
156
|
-
'overflow-y-clip',
|
|
157
|
-
'overflow-y-visible',
|
|
158
|
-
'overflow-y-scroll'
|
|
159
|
-
],
|
|
160
|
-
// Overscroll behavior
|
|
161
|
-
overscroll: ['overscroll-auto', 'overscroll-contain', 'overscroll-none'],
|
|
162
|
-
'overscroll-x': ['overscroll-x-auto', 'overscroll-x-contain', 'overscroll-x-none'],
|
|
163
|
-
'overscroll-y': ['overscroll-y-auto', 'overscroll-y-contain', 'overscroll-y-none'],
|
|
164
|
-
// Scroll behavior
|
|
165
|
-
'scroll-behavior': ['scroll-auto', 'scroll-smooth'],
|
|
166
|
-
// Text overflow
|
|
167
|
-
'text-overflow': ['truncate', 'text-ellipsis', 'text-clip'],
|
|
168
|
-
// Text wrap
|
|
169
|
-
'text-wrap': ['text-wrap', 'text-nowrap', 'text-balance', 'text-pretty'],
|
|
170
|
-
// Whitespace
|
|
171
|
-
whitespace: [
|
|
172
|
-
'whitespace-normal',
|
|
173
|
-
'whitespace-nowrap',
|
|
174
|
-
'whitespace-pre',
|
|
175
|
-
'whitespace-pre-line',
|
|
176
|
-
'whitespace-pre-wrap',
|
|
177
|
-
'whitespace-break-spaces'
|
|
178
|
-
],
|
|
179
|
-
// Word break
|
|
180
|
-
'word-break': ['break-normal', 'break-words', 'break-all', 'break-keep'],
|
|
181
|
-
// Hyphens
|
|
182
|
-
hyphens: ['hyphens-none', 'hyphens-manual', 'hyphens-auto'],
|
|
183
|
-
// List style position
|
|
184
|
-
'list-style-position': ['list-inside', 'list-outside'],
|
|
185
|
-
// Border collapse
|
|
186
|
-
'border-collapse': ['border-collapse', 'border-separate'],
|
|
187
|
-
// Table layout
|
|
188
|
-
'table-layout': ['table-auto', 'table-fixed'],
|
|
189
|
-
// Caption side
|
|
190
|
-
'caption-side': ['caption-top', 'caption-bottom'],
|
|
191
|
-
// Box decoration break
|
|
192
|
-
'box-decoration-break': ['box-decoration-clone', 'box-decoration-slice'],
|
|
193
|
-
// Box sizing
|
|
194
|
-
'box-sizing': ['box-border', 'box-content'],
|
|
195
|
-
// Isolation
|
|
196
|
-
isolation: ['isolate', 'isolation-auto'],
|
|
197
|
-
// Mix blend mode
|
|
198
|
-
'mix-blend-mode': [
|
|
199
|
-
'mix-blend-normal',
|
|
200
|
-
'mix-blend-multiply',
|
|
201
|
-
'mix-blend-screen',
|
|
202
|
-
'mix-blend-overlay',
|
|
203
|
-
'mix-blend-darken',
|
|
204
|
-
'mix-blend-lighten',
|
|
205
|
-
'mix-blend-color-dodge',
|
|
206
|
-
'mix-blend-color-burn',
|
|
207
|
-
'mix-blend-hard-light',
|
|
208
|
-
'mix-blend-soft-light',
|
|
209
|
-
'mix-blend-difference',
|
|
210
|
-
'mix-blend-exclusion',
|
|
211
|
-
'mix-blend-hue',
|
|
212
|
-
'mix-blend-saturation',
|
|
213
|
-
'mix-blend-color',
|
|
214
|
-
'mix-blend-luminosity',
|
|
215
|
-
'mix-blend-plus-darker',
|
|
216
|
-
'mix-blend-plus-lighter'
|
|
217
|
-
],
|
|
218
|
-
// Background blend mode
|
|
219
|
-
'background-blend-mode': [
|
|
220
|
-
'bg-blend-normal',
|
|
221
|
-
'bg-blend-multiply',
|
|
222
|
-
'bg-blend-screen',
|
|
223
|
-
'bg-blend-overlay',
|
|
224
|
-
'bg-blend-darken',
|
|
225
|
-
'bg-blend-lighten',
|
|
226
|
-
'bg-blend-color-dodge',
|
|
227
|
-
'bg-blend-color-burn',
|
|
228
|
-
'bg-blend-hard-light',
|
|
229
|
-
'bg-blend-soft-light',
|
|
230
|
-
'bg-blend-difference',
|
|
231
|
-
'bg-blend-exclusion',
|
|
232
|
-
'bg-blend-hue',
|
|
233
|
-
'bg-blend-saturation',
|
|
234
|
-
'bg-blend-color',
|
|
235
|
-
'bg-blend-luminosity'
|
|
236
|
-
],
|
|
237
|
-
// Background attachment
|
|
238
|
-
'background-attachment': ['bg-fixed', 'bg-local', 'bg-scroll'],
|
|
239
|
-
// Background clip
|
|
240
|
-
'background-clip': ['bg-clip-border', 'bg-clip-padding', 'bg-clip-content', 'bg-clip-text'],
|
|
241
|
-
// Background origin
|
|
242
|
-
'background-origin': ['bg-origin-border', 'bg-origin-padding', 'bg-origin-content'],
|
|
243
|
-
// Background repeat
|
|
244
|
-
'background-repeat': [
|
|
245
|
-
'bg-repeat',
|
|
246
|
-
'bg-no-repeat',
|
|
247
|
-
'bg-repeat-x',
|
|
248
|
-
'bg-repeat-y',
|
|
249
|
-
'bg-repeat-round',
|
|
250
|
-
'bg-repeat-space'
|
|
251
|
-
],
|
|
252
|
-
// Background size
|
|
253
|
-
'background-size': ['bg-auto', 'bg-cover', 'bg-contain'],
|
|
254
|
-
// Border style
|
|
255
|
-
'border-style': [
|
|
256
|
-
'border-solid',
|
|
257
|
-
'border-dashed',
|
|
258
|
-
'border-dotted',
|
|
259
|
-
'border-double',
|
|
260
|
-
'border-hidden',
|
|
261
|
-
'border-none'
|
|
262
|
-
],
|
|
263
|
-
// Outline style
|
|
264
|
-
'outline-style': [
|
|
265
|
-
'outline-none',
|
|
266
|
-
'outline',
|
|
267
|
-
'outline-dashed',
|
|
268
|
-
'outline-dotted',
|
|
269
|
-
'outline-double'
|
|
270
|
-
],
|
|
271
|
-
// Font style
|
|
272
|
-
'font-style': ['italic', 'not-italic'],
|
|
273
|
-
// Font variant numeric
|
|
274
|
-
'font-variant-numeric': [
|
|
275
|
-
'normal-nums',
|
|
276
|
-
'ordinal',
|
|
277
|
-
'slashed-zero',
|
|
278
|
-
'lining-nums',
|
|
279
|
-
'oldstyle-nums',
|
|
280
|
-
'proportional-nums',
|
|
281
|
-
'tabular-nums',
|
|
282
|
-
'diagonal-fractions',
|
|
283
|
-
'stacked-fractions'
|
|
284
|
-
],
|
|
285
|
-
// Text decoration line
|
|
286
|
-
'text-decoration-line': ['underline', 'overline', 'line-through', 'no-underline'],
|
|
287
|
-
// Text decoration style
|
|
288
|
-
'text-decoration-style': [
|
|
289
|
-
'decoration-solid',
|
|
290
|
-
'decoration-double',
|
|
291
|
-
'decoration-dotted',
|
|
292
|
-
'decoration-dashed',
|
|
293
|
-
'decoration-wavy'
|
|
294
|
-
],
|
|
295
|
-
// Text transform
|
|
296
|
-
'text-transform': ['uppercase', 'lowercase', 'capitalize', 'normal-case'],
|
|
297
|
-
// Vertical align
|
|
298
|
-
'vertical-align': [
|
|
299
|
-
'align-baseline',
|
|
300
|
-
'align-top',
|
|
301
|
-
'align-middle',
|
|
302
|
-
'align-bottom',
|
|
303
|
-
'align-text-top',
|
|
304
|
-
'align-text-bottom',
|
|
305
|
-
'align-sub',
|
|
306
|
-
'align-super'
|
|
307
|
-
],
|
|
308
|
-
// Resize
|
|
309
|
-
resize: ['resize-none', 'resize-y', 'resize-x', 'resize'],
|
|
310
|
-
// Scroll snap align
|
|
311
|
-
'scroll-snap-align': ['snap-start', 'snap-end', 'snap-center', 'snap-align-none'],
|
|
312
|
-
// Scroll snap stop
|
|
313
|
-
'scroll-snap-stop': ['snap-normal', 'snap-always'],
|
|
314
|
-
// Scroll snap type
|
|
315
|
-
'scroll-snap-type': ['snap-none', 'snap-x', 'snap-y', 'snap-both'],
|
|
316
|
-
// Scroll snap strictness (mandatory/proximity)
|
|
317
|
-
'scroll-snap-strictness': ['snap-mandatory', 'snap-proximity'],
|
|
318
|
-
// Touch action
|
|
319
|
-
'touch-action': [
|
|
320
|
-
'touch-auto',
|
|
321
|
-
'touch-none',
|
|
322
|
-
'touch-pan-x',
|
|
323
|
-
'touch-pan-left',
|
|
324
|
-
'touch-pan-right',
|
|
325
|
-
'touch-pan-y',
|
|
326
|
-
'touch-pan-up',
|
|
327
|
-
'touch-pan-down',
|
|
328
|
-
'touch-pinch-zoom',
|
|
329
|
-
'touch-manipulation'
|
|
330
|
-
],
|
|
331
|
-
// User select
|
|
332
|
-
'user-select': ['select-none', 'select-text', 'select-all', 'select-auto'],
|
|
333
|
-
// Pointer events
|
|
334
|
-
'pointer-events': ['pointer-events-none', 'pointer-events-auto'],
|
|
335
|
-
// Will change
|
|
336
|
-
'will-change': [
|
|
337
|
-
'will-change-auto',
|
|
338
|
-
'will-change-scroll',
|
|
339
|
-
'will-change-contents',
|
|
340
|
-
'will-change-transform'
|
|
341
|
-
],
|
|
342
|
-
// Appearance
|
|
343
|
-
appearance: ['appearance-none', 'appearance-auto'],
|
|
344
|
-
// Cursor
|
|
345
|
-
cursor: [
|
|
346
|
-
'cursor-auto',
|
|
347
|
-
'cursor-default',
|
|
348
|
-
'cursor-pointer',
|
|
349
|
-
'cursor-wait',
|
|
350
|
-
'cursor-text',
|
|
351
|
-
'cursor-move',
|
|
352
|
-
'cursor-help',
|
|
353
|
-
'cursor-not-allowed',
|
|
354
|
-
'cursor-none',
|
|
355
|
-
'cursor-context-menu',
|
|
356
|
-
'cursor-progress',
|
|
357
|
-
'cursor-cell',
|
|
358
|
-
'cursor-crosshair',
|
|
359
|
-
'cursor-vertical-text',
|
|
360
|
-
'cursor-alias',
|
|
361
|
-
'cursor-copy',
|
|
362
|
-
'cursor-no-drop',
|
|
363
|
-
'cursor-grab',
|
|
364
|
-
'cursor-grabbing',
|
|
365
|
-
'cursor-all-scroll',
|
|
366
|
-
'cursor-col-resize',
|
|
367
|
-
'cursor-row-resize',
|
|
368
|
-
'cursor-n-resize',
|
|
369
|
-
'cursor-e-resize',
|
|
370
|
-
'cursor-s-resize',
|
|
371
|
-
'cursor-w-resize',
|
|
372
|
-
'cursor-ne-resize',
|
|
373
|
-
'cursor-nw-resize',
|
|
374
|
-
'cursor-se-resize',
|
|
375
|
-
'cursor-sw-resize',
|
|
376
|
-
'cursor-ew-resize',
|
|
377
|
-
'cursor-ns-resize',
|
|
378
|
-
'cursor-nesw-resize',
|
|
379
|
-
'cursor-nwse-resize',
|
|
380
|
-
'cursor-zoom-in',
|
|
381
|
-
'cursor-zoom-out'
|
|
382
|
-
],
|
|
383
|
-
// Fill (SVG)
|
|
384
|
-
'fill-rule': ['fill-none'],
|
|
385
|
-
// Stroke linecap
|
|
386
|
-
'stroke-linecap': ['stroke-cap-auto', 'stroke-cap-square', 'stroke-cap-round'],
|
|
387
|
-
// Stroke linejoin
|
|
388
|
-
'stroke-linejoin': [
|
|
389
|
-
'stroke-join-auto',
|
|
390
|
-
'stroke-join-arcs',
|
|
391
|
-
'stroke-join-bevel',
|
|
392
|
-
'stroke-join-miter',
|
|
393
|
-
'stroke-join-miter-clip',
|
|
394
|
-
'stroke-join-round'
|
|
395
|
-
],
|
|
396
|
-
// Screen readers
|
|
397
|
-
'screen-reader': ['sr-only', 'not-sr-only'],
|
|
398
|
-
// Forced color adjust
|
|
399
|
-
'forced-color-adjust': ['forced-color-adjust-auto', 'forced-color-adjust-none']
|
|
400
|
-
};
|
|
7
|
+
const postcss_1 = __importDefault(require("postcss"));
|
|
401
8
|
/**
|
|
402
9
|
* Service for detecting conflicting Tailwind CSS utility classes.
|
|
403
|
-
*
|
|
10
|
+
* Uses Tailwind's design system to compile classes and analyze their CSS output.
|
|
11
|
+
* Two classes conflict when they set the same CSS properties in the same context.
|
|
404
12
|
*/
|
|
405
13
|
class TailwindConflictDetector {
|
|
406
14
|
constructor() {
|
|
407
|
-
this.
|
|
408
|
-
this.groupToClasses = new Map();
|
|
409
|
-
// Build reverse lookup maps
|
|
410
|
-
for (const [group, classes] of Object.entries(CONFLICT_GROUPS)) {
|
|
411
|
-
const classSet = new Set(classes);
|
|
412
|
-
this.groupToClasses.set(group, classSet);
|
|
413
|
-
for (const className of classes) {
|
|
414
|
-
this.classToGroup.set(className, group);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
15
|
+
this.cssProvider = null;
|
|
417
16
|
}
|
|
418
17
|
/**
|
|
419
|
-
*
|
|
420
|
-
* Returns the CSS property name that this class affects.
|
|
18
|
+
* Set the CSS provider (TailwindValidator)
|
|
421
19
|
*/
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
20
|
+
setCssProvider(provider) {
|
|
21
|
+
this.cssProvider = provider;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse CSS string and extract properties and context
|
|
25
|
+
*/
|
|
26
|
+
parseCss(css) {
|
|
27
|
+
const results = [];
|
|
28
|
+
try {
|
|
29
|
+
const root = postcss_1.default.parse(css);
|
|
30
|
+
// Walk through all rules and at-rules
|
|
31
|
+
const visit = (node, contextPath = []) => {
|
|
32
|
+
if (node.type === 'rule') {
|
|
33
|
+
// Extract properties from declarations
|
|
34
|
+
const properties = [];
|
|
35
|
+
node.nodes?.forEach(child => {
|
|
36
|
+
if (child.type === 'decl') {
|
|
37
|
+
properties.push(child.prop);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (properties.length > 0) {
|
|
41
|
+
results.push({
|
|
42
|
+
properties: properties.sort(),
|
|
43
|
+
context: contextPath.slice(1) // Skip the class selector itself
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Recursively visit nested rules
|
|
47
|
+
node.nodes?.forEach(child => {
|
|
48
|
+
if (child.type === 'rule' || child.type === 'atrule') {
|
|
49
|
+
const newContext = child.type === 'rule' ? child.selector : `@${child.name} ${child.params}`.trim();
|
|
50
|
+
visit(child, [...contextPath, newContext]);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else if (node.type === 'atrule') {
|
|
55
|
+
const atRuleContext = `@${node.name} ${node.params}`.trim();
|
|
56
|
+
node.nodes?.forEach(child => {
|
|
57
|
+
visit(child, [...contextPath, atRuleContext]);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
root.nodes?.forEach(node => {
|
|
62
|
+
if (node.type === 'rule') {
|
|
63
|
+
visit(node, [node.selector]);
|
|
64
|
+
}
|
|
65
|
+
else if (node.type === 'atrule') {
|
|
66
|
+
visit(node, []);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// If parsing fails, return empty results
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
427
74
|
}
|
|
428
75
|
/**
|
|
429
76
|
* Extract the prefix (variants) from a class name.
|
|
@@ -436,19 +83,39 @@ class TailwindConflictDetector {
|
|
|
436
83
|
return parts.slice(0, -1).join(':') + ':';
|
|
437
84
|
}
|
|
438
85
|
/**
|
|
439
|
-
*
|
|
440
|
-
* Handles responsive variants (sm:, md:, lg:, xl:, 2xl:) and state variants (hover:, focus:, etc.)
|
|
86
|
+
* Compare two ClassCssInfo arrays for equality
|
|
441
87
|
*/
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
88
|
+
cssInfoEquals(a, b) {
|
|
89
|
+
if (a.length !== b.length)
|
|
90
|
+
return false;
|
|
91
|
+
for (let i = 0; i < a.length; i++) {
|
|
92
|
+
const entryA = a[i];
|
|
93
|
+
const entryB = b[i];
|
|
94
|
+
// Compare properties
|
|
95
|
+
if (entryA.properties.length !== entryB.properties.length)
|
|
96
|
+
return false;
|
|
97
|
+
for (let j = 0; j < entryA.properties.length; j++) {
|
|
98
|
+
if (entryA.properties[j] !== entryB.properties[j])
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
// Compare context
|
|
102
|
+
if (entryA.context.length !== entryB.context.length)
|
|
103
|
+
return false;
|
|
104
|
+
for (let j = 0; j < entryA.context.length; j++) {
|
|
105
|
+
if (entryA.context[j] !== entryB.context[j])
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
446
110
|
}
|
|
447
111
|
/**
|
|
448
112
|
* Find all conflicting classes in a list of ClassNameInfo.
|
|
449
113
|
* Returns ConflictInfo for each class that conflicts with another.
|
|
450
114
|
*/
|
|
451
115
|
findConflicts(classNames) {
|
|
116
|
+
if (!this.cssProvider) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
452
119
|
const conflicts = [];
|
|
453
120
|
// Group classes by attributeId (conflicts are only within same attribute)
|
|
454
121
|
const byAttribute = new Map();
|
|
@@ -496,25 +163,19 @@ class TailwindConflictDetector {
|
|
|
496
163
|
const rootConflicts = this.findConflictsInList(rootClasses);
|
|
497
164
|
conflicts.push(...rootConflicts);
|
|
498
165
|
// 2. Find conflicts between root classes and branch classes
|
|
499
|
-
// (if a class is at root and conflicts with a class in ANY branch, that's a conflict)
|
|
500
166
|
for (const rootClass of rootClasses) {
|
|
501
|
-
const rootGroup = this.getConflictGroup(rootClass.className);
|
|
502
|
-
if (!rootGroup)
|
|
503
|
-
continue;
|
|
504
167
|
for (const branchClass of branchClasses) {
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
507
|
-
// Root and branch class conflict
|
|
168
|
+
const conflictProperty = this.getConflictProperty(rootClass.className, branchClass.className);
|
|
169
|
+
if (conflictProperty) {
|
|
508
170
|
conflicts.push({
|
|
509
171
|
classInfo: branchClass,
|
|
510
172
|
conflictsWith: [rootClass.className],
|
|
511
|
-
cssProperty:
|
|
173
|
+
cssProperty: conflictProperty
|
|
512
174
|
});
|
|
513
175
|
}
|
|
514
176
|
}
|
|
515
177
|
}
|
|
516
178
|
// 3. Find conflicts within the same branch
|
|
517
|
-
// Group branch classes by their branch ID
|
|
518
179
|
const byBranch = new Map();
|
|
519
180
|
for (const branchClass of branchClasses) {
|
|
520
181
|
const branchId = branchClass.conditionalBranchId;
|
|
@@ -527,85 +188,154 @@ class TailwindConflictDetector {
|
|
|
527
188
|
const branchConflicts = this.findConflictsInList(branchClassList);
|
|
528
189
|
conflicts.push(...branchConflicts);
|
|
529
190
|
}
|
|
530
|
-
// Note: We do NOT flag conflicts between true and false branches of a ternary
|
|
531
|
-
// because they are mutually exclusive at runtime
|
|
532
191
|
return conflicts;
|
|
533
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Get the CSS property that causes a conflict between two classes, or null if no conflict.
|
|
195
|
+
*/
|
|
196
|
+
getConflictProperty(className1, className2) {
|
|
197
|
+
if (!this.cssProvider)
|
|
198
|
+
return null;
|
|
199
|
+
if (className1 === className2)
|
|
200
|
+
return null;
|
|
201
|
+
// Classes with different prefixes don't conflict
|
|
202
|
+
const prefix1 = this.extractPrefix(className1);
|
|
203
|
+
const prefix2 = this.extractPrefix(className2);
|
|
204
|
+
if (prefix1 !== prefix2)
|
|
205
|
+
return null;
|
|
206
|
+
const cssResults = this.cssProvider.getCssForClasses([className1, className2]);
|
|
207
|
+
const css1 = cssResults[0];
|
|
208
|
+
const css2 = cssResults[1];
|
|
209
|
+
if (!css1 || !css2)
|
|
210
|
+
return null;
|
|
211
|
+
const info1 = this.parseCss(css1);
|
|
212
|
+
const info2 = this.parseCss(css2);
|
|
213
|
+
// Check if they have the same properties in the same context
|
|
214
|
+
if (this.cssInfoEquals(info1, info2)) {
|
|
215
|
+
// Return the properties as the conflict identifier
|
|
216
|
+
if (info1.length > 0 && info1[0].properties.length > 0) {
|
|
217
|
+
return info1[0].properties.join(', ');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
534
222
|
/**
|
|
535
223
|
* Find conflicts in a flat list of classes (no branch handling).
|
|
536
|
-
* Used for root classes or classes within the same branch.
|
|
537
|
-
*
|
|
538
|
-
* Classes with different prefixes (e.g., sm:text-left vs md:text-center) do NOT conflict
|
|
539
|
-
* because they apply at different breakpoints/states.
|
|
540
|
-
*
|
|
541
|
-
* All classes involved in a conflict are flagged, not just subsequent ones.
|
|
542
224
|
*/
|
|
543
225
|
findConflictsInList(classes) {
|
|
226
|
+
if (!this.cssProvider || classes.length === 0)
|
|
227
|
+
return [];
|
|
544
228
|
const conflicts = [];
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
229
|
+
// Get CSS for all classes at once (batch)
|
|
230
|
+
const classNames = classes.map(c => c.className);
|
|
231
|
+
const cssResults = this.cssProvider.getCssForClasses(classNames);
|
|
232
|
+
// Parse CSS and build info map
|
|
233
|
+
const classInfoMap = new Map();
|
|
234
|
+
for (let i = 0; i < classes.length; i++) {
|
|
235
|
+
const classInfo = classes[i];
|
|
236
|
+
const css = cssResults[i];
|
|
237
|
+
if (css) {
|
|
238
|
+
classInfoMap.set(classInfo, {
|
|
239
|
+
cssInfo: this.parseCss(css),
|
|
240
|
+
prefix: this.extractPrefix(classInfo.className)
|
|
241
|
+
});
|
|
556
242
|
}
|
|
557
|
-
byGroupAndPrefix.get(key).push(classInfo);
|
|
558
243
|
}
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
.
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
244
|
+
// Compare all pairs
|
|
245
|
+
const classesWithCss = [...classInfoMap.keys()];
|
|
246
|
+
const conflictMap = new Map();
|
|
247
|
+
for (let i = 0; i < classesWithCss.length; i++) {
|
|
248
|
+
for (let j = i + 1; j < classesWithCss.length; j++) {
|
|
249
|
+
const classA = classesWithCss[i];
|
|
250
|
+
const classB = classesWithCss[j];
|
|
251
|
+
// Skip duplicate classes (same class name) - duplicates are handled separately
|
|
252
|
+
if (classA.className === classB.className)
|
|
253
|
+
continue;
|
|
254
|
+
const infoA = classInfoMap.get(classA);
|
|
255
|
+
const infoB = classInfoMap.get(classB);
|
|
256
|
+
// Different prefixes don't conflict
|
|
257
|
+
if (infoA.prefix !== infoB.prefix)
|
|
258
|
+
continue;
|
|
259
|
+
// Check if same CSS structure (properties + context)
|
|
260
|
+
if (this.cssInfoEquals(infoA.cssInfo, infoB.cssInfo)) {
|
|
261
|
+
const cssProperty = infoA.cssInfo.length > 0 && infoA.cssInfo[0].properties.length > 0
|
|
262
|
+
? infoA.cssInfo[0].properties.join(', ')
|
|
263
|
+
: 'unknown';
|
|
264
|
+
// Add to conflict map for classA
|
|
265
|
+
if (!conflictMap.has(classA)) {
|
|
266
|
+
conflictMap.set(classA, new Set());
|
|
267
|
+
}
|
|
268
|
+
conflictMap.get(classA).add(classB.className);
|
|
269
|
+
// Add to conflict map for classB
|
|
270
|
+
if (!conflictMap.has(classB)) {
|
|
271
|
+
conflictMap.set(classB, new Set());
|
|
272
|
+
}
|
|
273
|
+
conflictMap.get(classB).add(classA.className);
|
|
274
|
+
// Create conflict entries (we'll dedupe below)
|
|
275
|
+
const existingA = conflicts.find(c => c.classInfo === classA);
|
|
276
|
+
const existingB = conflicts.find(c => c.classInfo === classB);
|
|
277
|
+
if (existingA) {
|
|
278
|
+
existingA.conflictsWith.push(classB.className);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
conflicts.push({
|
|
282
|
+
classInfo: classA,
|
|
283
|
+
conflictsWith: [classB.className],
|
|
284
|
+
cssProperty
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (existingB) {
|
|
288
|
+
existingB.conflictsWith.push(classA.className);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
conflicts.push({
|
|
292
|
+
classInfo: classB,
|
|
293
|
+
conflictsWith: [classA.className],
|
|
294
|
+
cssProperty
|
|
295
|
+
});
|
|
296
|
+
}
|
|
585
297
|
}
|
|
586
298
|
}
|
|
587
299
|
}
|
|
300
|
+
// Dedupe conflictsWith arrays
|
|
301
|
+
for (const conflict of conflicts) {
|
|
302
|
+
conflict.conflictsWith = [...new Set(conflict.conflictsWith)];
|
|
303
|
+
}
|
|
588
304
|
return conflicts;
|
|
589
305
|
}
|
|
590
306
|
/**
|
|
591
307
|
* Check if two class names conflict with each other.
|
|
592
308
|
*/
|
|
593
309
|
doClassesConflict(className1, className2) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
310
|
+
return this.getConflictProperty(className1, className2) !== null;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get the conflict group for a class name, if any.
|
|
314
|
+
* Returns the CSS properties that this class affects.
|
|
315
|
+
*/
|
|
316
|
+
getConflictGroup(className) {
|
|
317
|
+
if (!this.cssProvider)
|
|
318
|
+
return null;
|
|
319
|
+
const cssResults = this.cssProvider.getCssForClasses([className]);
|
|
320
|
+
const css = cssResults[0];
|
|
321
|
+
if (!css)
|
|
322
|
+
return null;
|
|
323
|
+
const info = this.parseCss(css);
|
|
324
|
+
if (info.length > 0 && info[0].properties.length > 0) {
|
|
325
|
+
return info[0].properties.join(', ');
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
597
328
|
}
|
|
598
329
|
/**
|
|
599
330
|
* Get all classes that would conflict with a given class.
|
|
331
|
+
* Note: This is a simplified implementation that returns an empty array
|
|
332
|
+
* since we no longer have a static conflict group map.
|
|
600
333
|
*/
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
601
335
|
getConflictingClasses(className) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const groupClasses = this.groupToClasses.get(group);
|
|
606
|
-
if (!groupClasses)
|
|
607
|
-
return [];
|
|
608
|
-
return [...groupClasses].filter(c => c !== this.extractBaseClass(className));
|
|
336
|
+
// Without a full class list, we can't enumerate all conflicting classes
|
|
337
|
+
// This would require iterating over all possible Tailwind classes
|
|
338
|
+
return [];
|
|
609
339
|
}
|
|
610
340
|
}
|
|
611
341
|
exports.TailwindConflictDetector = TailwindConflictDetector;
|