11import type { CSSEntries , DynamicMatcher , Preset , RuleContext } from 'unocss'
22import { cornerMap , directionSize , h } from '@unocss/preset-wind4/utils'
33
4+ export type CollectorChecker = ( warning : string , rule : string ) => void
5+
46// Track warnings to avoid duplicates
57const warnedClasses = new Set < string > ( )
68
@@ -38,6 +40,7 @@ const directionMap: Record<string, string[]> = {
3840function directionSizeRTL (
3941 propertyPrefix : string ,
4042 prefixMap ?: { l : string ; r : string } ,
43+ checker ?: CollectorChecker ,
4144) : DynamicMatcher {
4245 const matcher = directionSize ( propertyPrefix )
4346 return ( args , context ) => {
@@ -46,10 +49,16 @@ function directionSizeRTL(
4649 const defaultMap = { l : 'is' , r : 'ie' }
4750 const map = prefixMap || defaultMap
4851 const replacement = map [ direction as 'l' | 'r' ]
49- warnOnce (
50- `[RTL] Avoid using '${ match } '. Use '${ match . replace ( direction === 'l' ? 'l' : 'r' , replacement ) } ' instead.` ,
51- match ,
52- )
52+
53+ const fullClass = context . rawSelector || match
54+ const suggestedBase = match . replace ( direction === 'l' ? 'l' : 'r' , replacement )
55+ const suggestedClass = fullClass . replace ( match , suggestedBase )
56+
57+ if ( checker ) {
58+ checker ( `avoid using '${ fullClass } ', use '${ suggestedClass } ' instead.` , fullClass )
59+ } else {
60+ warnOnce ( `[RTL] Avoid using '${ fullClass } '. Use '${ suggestedClass } ' instead.` , fullClass )
61+ }
5362 return matcher ( [ match , replacement , size ] , context )
5463 }
5564}
@@ -78,74 +87,108 @@ function handlerBorderSize([, a = '', b = '1']: string[]): CSSEntries | undefine
7887/**
7988 * CSS RTL support to detect, replace and warn wrong left/right usages.
8089 */
81- export function presetRtl ( ) : Preset {
90+ export function presetRtl ( checker ?: CollectorChecker ) : Preset {
8291 return {
8392 name : 'rtl-preset' ,
8493 rules : [
8594 // RTL overrides
8695 // We need to move the dash out of the capturing group to avoid capturing it in the direction
8796 [
8897 / ^ p ( [ r l ] ) - ( .+ ) ? $ / ,
89- directionSizeRTL ( 'padding' , { l : 's' , r : 'e' } ) ,
98+ directionSizeRTL ( 'padding' , { l : 's' , r : 'e' } , checker ) ,
9099 { autocomplete : '(m|p)<directions>-<num>' } ,
91100 ] ,
92101 [
93102 / ^ m ( [ r l ] ) - ( .+ ) ? $ / ,
94- directionSizeRTL ( 'margin' , { l : 's' , r : 'e' } ) ,
103+ directionSizeRTL ( 'margin' , { l : 's' , r : 'e' } , checker ) ,
95104 { autocomplete : '(m|p)<directions>-<num>' } ,
96105 ] ,
97106 [
98107 / ^ (?: p o s i t i o n - | p o s - ) ? ( l e f t | r i g h t ) - ( .+ ) $ / ,
99- ( [ , direction , size ] , context ) => {
108+ ( [ match , direction , size ] , context ) => {
100109 if ( ! size ) return undefined
101110 const replacement = direction === 'left' ? 'inset-is' : 'inset-ie'
102- warnOnce (
103- `[RTL] Avoid using '${ direction } -${ size } '. Use '${ replacement } -${ size } ' instead.` ,
104- `${ direction } -${ size } ` ,
105- )
111+
112+ const fullClass = context . rawSelector || match
113+ const suggestedBase = match . replace ( direction ! , replacement )
114+ const suggestedClass = fullClass . replace ( match , suggestedBase )
115+
116+ if ( checker ) {
117+ checker ( `avoid using '${ fullClass } '. Use '${ suggestedClass } ' instead.` , fullClass )
118+ } else {
119+ warnOnce (
120+ `[RTL] Avoid using '${ fullClass } '. Use '${ suggestedClass } ' instead.` ,
121+ fullClass ,
122+ )
123+ }
106124 return directionSize ( 'inset' ) ( [ '' , direction === 'left' ? 'is' : 'ie' , size ] , context )
107125 } ,
108126 { autocomplete : '(left|right)-<num>' } ,
109127 ] ,
110128 [
111129 / ^ t e x t - ( l e f t | r i g h t ) $ / ,
112- ( [ , direction ] ) => {
130+ ( [ match , direction ] , context ) => {
113131 const replacement = direction === 'left' ? 'start' : 'end'
114- warnOnce (
115- `[RTL] Avoid using 'text-${ direction } '. Use 'text-${ replacement } ' instead.` ,
116- `text-${ direction } ` ,
117- )
132+
133+ const fullClass = context . rawSelector || match
134+ const suggestedBase = match . replace ( direction ! , replacement )
135+ const suggestedClass = fullClass . replace ( match , suggestedBase )
136+
137+ if ( checker ) {
138+ checker ( `avoid using '${ fullClass } ', use '${ suggestedClass } ' instead.` , fullClass )
139+ } else {
140+ warnOnce (
141+ `[RTL] Avoid using '${ fullClass } '. Use '${ suggestedClass } ' instead.` ,
142+ fullClass ,
143+ )
144+ }
118145 return { 'text-align' : replacement }
119146 } ,
120147 { autocomplete : 'text-(left|right)' } ,
121148 ] ,
122149 [
123150 / ^ r o u n d e d - ( [ r l ] ) (?: - ( .+ ) ) ? $ / ,
124- ( args , context ) => {
125- const [ _ , direction , size ] = args
151+ ( [ match , direction , size ] , context ) => {
126152 if ( ! direction ) return undefined
127153 const replacementMap : Record < string , string > = {
128154 l : 'is' ,
129155 r : 'ie' ,
130156 }
131157 const replacement = replacementMap [ direction ]
132158 if ( ! replacement ) return undefined
133- warnOnce (
134- `[RTL] Avoid using 'rounded-${ direction } '. Use 'rounded-${ replacement } ' instead.` ,
135- `rounded-${ direction } ` ,
136- )
159+
160+ const fullClass = context . rawSelector || match
161+ const suggestedBase = match . replace ( direction ! , replacement )
162+ const suggestedClass = fullClass . replace ( match , suggestedBase )
163+
164+ if ( checker ) {
165+ checker ( `avoid using '${ fullClass } ', use '${ suggestedClass } ' instead.` , fullClass )
166+ } else {
167+ warnOnce (
168+ `[RTL] Avoid using '${ fullClass } '. Use '${ suggestedClass } ' instead.` ,
169+ fullClass ,
170+ )
171+ }
137172 return handlerRounded ( [ '' , replacement , size ?? 'DEFAULT' ] , context )
138173 } ,
139174 ] ,
140175 [
141176 / ^ b o r d e r - ( [ r l ] ) (?: - ( .+ ) ) ? $ / ,
142- args => {
143- const [ _ , direction , size ] = args
177+ ( [ match , direction , size ] , context ) => {
144178 const replacement = direction === 'l' ? 'is' : 'ie'
145- warnOnce (
146- `[RTL] Avoid using 'border-${ direction } '. Use 'border-${ replacement } ' instead.` ,
147- `border-${ direction } ` ,
148- )
179+
180+ const fullClass = context . rawSelector || match
181+ const suggestedBase = match . replace ( direction ! , replacement )
182+ const suggestedClass = fullClass . replace ( match , suggestedBase )
183+
184+ if ( checker ) {
185+ checker ( `avoid using '${ fullClass } ', use '${ suggestedClass } ' instead.` , fullClass )
186+ } else {
187+ warnOnce (
188+ `[RTL] Avoid using '${ fullClass } '. Use '${ suggestedClass } ' instead.` ,
189+ fullClass ,
190+ )
191+ }
149192 return handlerBorderSize ( [ '' , replacement , size || '1' ] )
150193 } ,
151194 ] ,
0 commit comments