@@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser'
3
3
import parseObjectStyles from '../util/parseObjectStyles'
4
4
import isPlainObject from '../util/isPlainObject'
5
5
import prefixSelector from '../util/prefixSelector'
6
- import { updateAllClasses } from '../util/pluginUtils'
6
+ import { updateAllClasses , typeMap } from '../util/pluginUtils'
7
7
import log from '../util/log'
8
8
import * as sharedState from './sharedState'
9
9
import { formatVariantSelector , finalizeSelector } from '../util/formatVariantSelector'
@@ -535,68 +535,135 @@ function* resolveMatches(candidate, context, original = candidate) {
535
535
}
536
536
537
537
if ( matchesPerPlugin . length > 0 ) {
538
- typesByMatches . set ( matchesPerPlugin , sort . options ?. type )
538
+ let matchingTypes = ( sort . options ?. types ?? [ ] )
539
+ . map ( ( { type } ) => type )
540
+ // Only track the types for this plugin that resulted in some result
541
+ . filter ( ( type ) => {
542
+ return Boolean (
543
+ typeMap [ type ] ( modifier , sort . options , {
544
+ tailwindConfig : context . tailwindConfig ,
545
+ } )
546
+ )
547
+ } )
548
+
549
+ if ( matchingTypes . length > 0 ) {
550
+ typesByMatches . set ( matchesPerPlugin , matchingTypes )
551
+ }
552
+
539
553
matches . push ( matchesPerPlugin )
540
554
}
541
555
}
542
556
543
557
if ( isArbitraryValue ( modifier ) ) {
544
- // When generated arbitrary values are ambiguous, we can't know
545
- // which to pick so don't generate any utilities for them
546
558
if ( matches . length > 1 ) {
547
- let typesPerPlugin = matches . map ( ( match ) => new Set ( [ ...( typesByMatches . get ( match ) ?? [ ] ) ] ) )
559
+ // Partition plugins in 2 categories so that we can start searching in the plugins that
560
+ // don't have `any` as a type first.
561
+ let [ withAny , withoutAny ] = matches . reduce (
562
+ ( group , plugin ) => {
563
+ let hasAnyType = plugin . some ( ( [ { options } ] ) =>
564
+ options . types . some ( ( { type } ) => type === 'any' )
565
+ )
548
566
549
- // Remove duplicates, so that we can detect proper unique types for each plugin.
550
- for ( let pluginTypes of typesPerPlugin ) {
551
- for ( let type of pluginTypes ) {
552
- let removeFromOwnGroup = false
567
+ if ( hasAnyType ) {
568
+ group [ 0 ] . push ( plugin )
569
+ } else {
570
+ group [ 1 ] . push ( plugin )
571
+ }
572
+ return group
573
+ } ,
574
+ [ [ ] , [ ] ]
575
+ )
553
576
554
- for ( let otherGroup of typesPerPlugin ) {
555
- if ( pluginTypes === otherGroup ) continue
577
+ function findFallback ( matches ) {
578
+ // If only a single plugin matches, let's take that one
579
+ if ( matches . length === 1 ) {
580
+ return matches [ 0 ]
581
+ }
556
582
557
- if ( otherGroup . has ( type ) ) {
558
- otherGroup . delete ( type )
559
- removeFromOwnGroup = true
583
+ // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
584
+ // also has the correct type which disambiguates the plugin in case of clashes.
585
+ return matches . find ( ( rules ) => {
586
+ let matchingTypes = typesByMatches . get ( rules )
587
+ return rules . some ( ( [ { options } , rule ] ) => {
588
+ if ( ! isParsableNode ( rule ) ) {
589
+ return false
560
590
}
561
- }
562
591
563
- if ( removeFromOwnGroup ) pluginTypes . delete ( type )
564
- }
592
+ return options . types . some (
593
+ ( { type, disambiguate } ) => matchingTypes . includes ( type ) && disambiguate
594
+ )
595
+ } )
596
+ } )
565
597
}
566
598
567
- let messages = [ ]
568
-
569
- for ( let [ idx , group ] of typesPerPlugin . entries ( ) ) {
570
- for ( let type of group ) {
571
- let rules = matches [ idx ]
572
- . map ( ( [ , rule ] ) => rule )
573
- . flat ( )
574
- . map ( ( rule ) =>
575
- rule
576
- . toString ( )
577
- . split ( '\n' )
578
- . slice ( 1 , - 1 ) // Remove selector and closing ' }'
579
- . map ( ( line ) => line . trim ( ) )
580
- . map ( ( x ) => ` ${ x } ` ) // Re-indent
581
- . join ( '\n' )
582
- )
583
- . join ( '\n\n' )
599
+ // Try to find a fallback plugin, because we already know that multiple plugins matched for
600
+ // the given arbitrary value.
601
+ let fallback = findFallback ( withoutAny ) ?? findFallback ( withAny )
602
+ if ( fallback ) {
603
+ matches = [ fallback ]
604
+ }
584
605
585
- messages . push (
586
- ` Use \`${ candidate . replace ( '[' , `[${ type } :` ) } \` for \`${ rules . trim ( ) } \``
587
- )
588
- break
606
+ // We couldn't find a fallback plugin which means that there are now multiple plugins that
607
+ // generated css for the current candidate. This means that the result is ambiguous and this
608
+ // should not happen. We won't generate anything right now, so let's report this to the user
609
+ // by logging some options about what they can do.
610
+ else {
611
+ let typesPerPlugin = matches . map (
612
+ ( match ) => new Set ( [ ...( typesByMatches . get ( match ) ?? [ ] ) ] )
613
+ )
614
+
615
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
616
+ for ( let pluginTypes of typesPerPlugin ) {
617
+ for ( let type of pluginTypes ) {
618
+ let removeFromOwnGroup = false
619
+
620
+ for ( let otherGroup of typesPerPlugin ) {
621
+ if ( pluginTypes === otherGroup ) continue
622
+
623
+ if ( otherGroup . has ( type ) ) {
624
+ otherGroup . delete ( type )
625
+ removeFromOwnGroup = true
626
+ }
627
+ }
628
+
629
+ if ( removeFromOwnGroup ) pluginTypes . delete ( type )
630
+ }
589
631
}
590
- }
591
632
592
- log . warn ( [
593
- `The class \`${ candidate } \` is ambiguous and matches multiple utilities.` ,
594
- ...messages ,
595
- `If this is content and not a class, replace it with \`${ candidate
596
- . replace ( '[' , '[' )
597
- . replace ( ']' , ']' ) } \` to silence this warning.`,
598
- ] )
599
- continue
633
+ let messages = [ ]
634
+
635
+ for ( let [ idx , group ] of typesPerPlugin . entries ( ) ) {
636
+ for ( let type of group ) {
637
+ let rules = matches [ idx ]
638
+ . map ( ( [ , rule ] ) => rule )
639
+ . flat ( )
640
+ . map ( ( rule ) =>
641
+ rule
642
+ . toString ( )
643
+ . split ( '\n' )
644
+ . slice ( 1 , - 1 ) // Remove selector and closing ' }'
645
+ . map ( ( line ) => line . trim ( ) )
646
+ . map ( ( x ) => ` ${ x } ` ) // Re-indent
647
+ . join ( '\n' )
648
+ )
649
+ . join ( '\n\n' )
650
+
651
+ messages . push (
652
+ ` Use \`${ candidate . replace ( '[' , `[${ type } :` ) } \` for \`${ rules . trim ( ) } \``
653
+ )
654
+ break
655
+ }
656
+ }
657
+
658
+ log . warn ( [
659
+ `The class \`${ candidate } \` is ambiguous and matches multiple utilities.` ,
660
+ ...messages ,
661
+ `If this is content and not a class, replace it with \`${ candidate
662
+ . replace ( '[' , '[' )
663
+ . replace ( ']' , ']' ) } \` to silence this warning.`,
664
+ ] )
665
+ continue
666
+ }
600
667
}
601
668
602
669
matches = matches . map ( ( list ) => list . filter ( ( match ) => isParsableNode ( match [ 1 ] ) ) )
0 commit comments