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.
Files changed (34) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +6 -6
  3. package/lib/extractors/TailwindVariantsExtractor.d.ts.map +1 -1
  4. package/lib/extractors/TailwindVariantsExtractor.js +0 -4
  5. package/lib/extractors/TailwindVariantsExtractor.js.map +1 -1
  6. package/lib/infrastructure/TailwindConflictDetector.d.ts +28 -18
  7. package/lib/infrastructure/TailwindConflictDetector.d.ts.map +1 -1
  8. package/lib/infrastructure/TailwindConflictDetector.js +216 -486
  9. package/lib/infrastructure/TailwindConflictDetector.js.map +1 -1
  10. package/lib/infrastructure/TailwindValidator.d.ts +6 -0
  11. package/lib/infrastructure/TailwindValidator.d.ts.map +1 -1
  12. package/lib/infrastructure/TailwindValidator.js +19 -0
  13. package/lib/infrastructure/TailwindValidator.js.map +1 -1
  14. package/lib/plugin/TailwindTypescriptPlugin.d.ts +5 -0
  15. package/lib/plugin/TailwindTypescriptPlugin.d.ts.map +1 -1
  16. package/lib/plugin/TailwindTypescriptPlugin.js +12 -1
  17. package/lib/plugin/TailwindTypescriptPlugin.js.map +1 -1
  18. package/lib/services/ConflictClassDetection.spec.js +24 -19
  19. package/lib/services/ConflictClassDetection.spec.js.map +1 -1
  20. package/lib/services/DuplicateClassDetection.spec.js +41 -60
  21. package/lib/services/DuplicateClassDetection.spec.js.map +1 -1
  22. package/lib/services/ValidationService.d.ts +2 -1
  23. package/lib/services/ValidationService.d.ts.map +1 -1
  24. package/lib/services/ValidationService.js +10 -7
  25. package/lib/services/ValidationService.js.map +1 -1
  26. package/package.json +3 -1
  27. package/lib/extractors/StringLiteralExtractor.d.ts +0 -12
  28. package/lib/extractors/StringLiteralExtractor.d.ts.map +0 -1
  29. package/lib/extractors/StringLiteralExtractor.js +0 -21
  30. package/lib/extractors/StringLiteralExtractor.js.map +0 -1
  31. package/lib/services/ClassNameExtractionService.original.d.ts +0 -20
  32. package/lib/services/ClassNameExtractionService.original.d.ts.map +0 -1
  33. package/lib/services/ClassNameExtractionService.original.js +0 -48
  34. 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
- * Conflicting classes are utilities that set the same CSS property to different values.
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.classToGroup = new Map();
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
- * Get the conflict group for a class name, if any.
420
- * Returns the CSS property name that this class affects.
18
+ * Set the CSS provider (TailwindValidator)
421
19
  */
422
- getConflictGroup(className) {
423
- // Handle responsive/state variants by extracting base class
424
- // e.g., "md:text-left" -> "text-left", "hover:flex" -> "flex"
425
- const baseClass = this.extractBaseClass(className);
426
- return this.classToGroup.get(baseClass) ?? null;
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
- * Extract the base class from a potentially prefixed class name.
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
- extractBaseClass(className) {
443
- // Split by colon and take the last part (the actual utility)
444
- const parts = className.split(':');
445
- return parts[parts.length - 1];
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 branchGroup = this.getConflictGroup(branchClass.className);
506
- if (branchGroup === rootGroup && rootClass.className !== branchClass.className) {
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: rootGroup
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
- // Group classes by their conflict group AND prefix
546
- // Key format: "group:prefix" (e.g., "text-align:" or "text-align:md:")
547
- const byGroupAndPrefix = new Map();
548
- for (const classInfo of classes) {
549
- const group = this.getConflictGroup(classInfo.className);
550
- if (!group)
551
- continue;
552
- const prefix = this.extractPrefix(classInfo.className);
553
- const key = `${group}:${prefix}`;
554
- if (!byGroupAndPrefix.has(key)) {
555
- byGroupAndPrefix.set(key, []);
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
- // For each group+prefix with multiple classes, create conflict entries
560
- for (const [key, groupClasses] of byGroupAndPrefix) {
561
- if (groupClasses.length <= 1)
562
- continue;
563
- // Extract the group name from the key
564
- const group = key.split(':')[0];
565
- // Get unique base class names in this conflict (without prefix)
566
- const uniqueBaseClassNames = [
567
- ...new Set(groupClasses.map(c => this.extractBaseClass(c.className)))
568
- ];
569
- if (uniqueBaseClassNames.length <= 1)
570
- continue; // Same class repeated, not a conflict (handled by duplicate detection)
571
- // Mark ALL classes in the conflict group as conflicting
572
- // Each class shows what other classes it conflicts with
573
- for (const currentClass of groupClasses) {
574
- const currentBaseClass = this.extractBaseClass(currentClass.className);
575
- const conflictingClasses = groupClasses
576
- .filter(c => this.extractBaseClass(c.className) !== currentBaseClass)
577
- .map(c => c.className);
578
- // Only add if it actually conflicts with a different class
579
- if (conflictingClasses.length > 0) {
580
- conflicts.push({
581
- classInfo: currentClass,
582
- conflictsWith: [...new Set(conflictingClasses)],
583
- cssProperty: group
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
- const group1 = this.getConflictGroup(className1);
595
- const group2 = this.getConflictGroup(className2);
596
- return group1 !== null && group1 === group2 && className1 !== className2;
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
- const group = this.getConflictGroup(className);
603
- if (!group)
604
- return [];
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;