@@ -63,14 +63,23 @@ export const createRule = RuleCreator(urlCreator);
6363const arbitraryRegEx =
6464 / ^ (?< classPrefix > [ ^ [ ] + ) - \[ (?< arbitraryValue > [ ^ \] ] + ) \] ! ? $ / u;
6565
66+ /**
67+ * Determines final signs and absolute text fragments from arbitrary value configurations.
68+ */
69+ function resolveValueSign ( value : string , initiallyNegative : boolean ) {
70+ const isHyphen = value . charCodeAt ( 0 ) === 45 ; // '-'
71+ const finalIsNegative = isHyphen ? ! initiallyNegative : initiallyNegative ;
72+ const cleanedValue = isHyphen ? value . slice ( 1 ) : value ;
73+ return { minusSign : finalIsNegative ? "-" : "" , cleanedValue } ;
74+ }
75+
6676const checkArbitraryClassnames = (
6777 context : RuleContext ,
6878 settings : PluginSettings ,
6979 options : RuleOptions ,
7080 literals : Array < AtomicNode > ,
7181) => {
7282 const genericContext = context as unknown as GenericRuleContext ;
73-
7483 const theme = loadThemeWorker ( settings . cssConfigPath , context . filename ) ;
7584
7685 for ( const node of literals ) {
@@ -87,6 +96,7 @@ const checkArbitraryClassnames = (
8796 // e.g. "dark:-m-[5px]" → "-m-[5px]"
8897 let baseClass = getBaseClassname ( targetClassName ) ;
8998 let bang = "" ;
99+
90100 if ( baseClass . startsWith ( "!" ) ) {
91101 bang = "!" ;
92102 baseClass = baseClass . slice ( 1 ) ;
@@ -95,12 +105,11 @@ const checkArbitraryClassnames = (
95105 bang = "!" ;
96106 baseClass = baseClass . slice ( 0 , - 1 ) ;
97107 }
108+
98109 const negativePrefix = baseClass . startsWith ( "-" ) ? "-" : "" ;
99110 // e.g. "-m-[5px]" → "m-[5px]"
100111 const absBaseClass = negativePrefix ? baseClass . slice ( 1 ) : baseClass ;
101112
102- const debug = absBaseClass === "my-[2px]!" ;
103-
104113 // e.g. "m-[5px]" → { classPrefix: "m", arbitraryValue: "5px" }
105114 const match = absBaseClass . match ( arbitraryRegEx ) ;
106115 if ( ! match ?. groups ) continue ;
@@ -111,63 +120,45 @@ const checkArbitraryClassnames = (
111120 // Get the config prefix based on the classname
112121 // e.g. "divide-x-abc" → ["--color-", "--border-color-", "--divide-color-"]
113122 const prefixes = getThemeKeyPrefixesFromClassname ( absBaseClass ) ;
114-
115123 // Should not happen, but just in case
116124 if ( prefixes . size === 0 ) continue ;
117125
118126 // Retrieves all the possible keys
119127 const presetKeys = getThemePresetsFromPrefixes ( theme , prefixes ) ;
120-
121128 const acceptGenericNumberSuffix = allowsGenericNumbers ( absBaseClass ) ;
122-
123129 const { classPrefix = "" , arbitraryValue = "" } = match . groups ;
124-
125130 const compressedArbitraryValue =
126131 compressTailwindArbitrary ( arbitraryValue ) ;
127132 const matchingPresets : Array < string > = [ ] ;
128133
129- if ( debug )
130- console . log ( "compressedArbitraryValue" , [ compressedArbitraryValue ] ) ;
131-
132- // 1. Check for px native presets e.g. `my-[1px]` → `my-px` (lowest specificity)
134+ // 1. Check for px native presets e.g. `my-[1px]` → `my-px`
133135 if (
134136 [ "-1px" , "1px" ] . includes ( compressedArbitraryValue ) &&
135137 hasPxNativePreset ( absBaseClass )
136138 ) {
137- if ( debug ) console . log ( "#1" ) ;
138- let isNegative = ! ! negativePrefix ;
139- // Faster than `.startsWith("-")`
140- // eslint-disable-next-line unicorn/prefer-code-point
141- if ( compressedArbitraryValue . charCodeAt ( 0 ) === 45 )
142- isNegative = ! isNegative ;
143- const minus = isNegative ? "-" : "" ;
144- matchingPresets . push ( `${ modifiers } ${ minus } ${ classPrefix } -px` ) ;
139+ const { minusSign } = resolveValueSign (
140+ compressedArbitraryValue ,
141+ ! ! negativePrefix ,
142+ ) ;
143+ matchingPresets . push ( `${ modifiers } ${ minusSign } ${ classPrefix } -px` ) ;
145144 }
146145
147146 // 2. Looking into the defined presets for EXACT preset matches
148- for ( const prefix of prefixes ) {
147+ for ( const currentPrefix of prefixes ) {
149148 for ( const [ presetKey , presetValue ] of presetKeys . entries ( ) ) {
150149 // Does not match
151- if ( ! presetKey . startsWith ( prefix ) ) continue ;
150+ if ( ! presetKey . startsWith ( currentPrefix ) ) continue ;
152151
153152 const compressedPresetValue = toTailwindArbitrary ( presetValue ) ;
154- let computedValue = compressedArbitraryValue ;
155- let isNegative = ! ! negativePrefix ;
156- // Faster than `.startsWith("-")`
157- // eslint-disable-next-line unicorn/prefer-code-point
158- if ( compressedArbitraryValue . charCodeAt ( 0 ) === 45 ) {
159- computedValue = computedValue . slice ( 1 ) ;
160- isNegative = ! isNegative ;
161- }
162- if ( compressedPresetValue === computedValue ) {
163- const presetName = presetKey . slice ( prefix . length ) ;
164- const minus = isNegative ? "-" : "" ;
165- if ( debug ) {
166- console . log ( "#2" , [ presetKey ] , [ presetValue ] , [ presetName ] ) ;
167- console . log ( [ `${ modifiers } ${ minus } ${ classPrefix } -${ presetName } ` ] ) ;
168- }
153+ const { minusSign, cleanedValue } = resolveValueSign (
154+ compressedArbitraryValue ,
155+ ! ! negativePrefix ,
156+ ) ;
157+
158+ if ( compressedPresetValue === cleanedValue ) {
159+ const presetName = presetKey . slice ( currentPrefix . length ) ;
169160 matchingPresets . push (
170- `${ modifiers } ${ minus } ${ classPrefix } -${ presetName } ${ bang } ` ,
161+ `${ modifiers } ${ minusSign } ${ classPrefix } -${ presetName } ${ bang } ` ,
171162 ) ;
172163 }
173164 }
@@ -178,59 +169,44 @@ const checkArbitraryClassnames = (
178169 acceptGenericNumberSuffix &&
179170 / ^ - ? \d + (?: \. \d + ) ? $ / . test ( compressedArbitraryValue )
180171 ) {
181- let computedValue = compressedArbitraryValue ;
182- let isNegative = ! ! negativePrefix ;
183- // Faster than `.startsWith("-")`
184- // eslint-disable-next-line unicorn/prefer-code-point
185- if ( compressedArbitraryValue . charCodeAt ( 0 ) === 45 ) {
186- computedValue = computedValue . slice ( 1 ) ;
187- isNegative = ! isNegative ;
188- }
189- const minus = isNegative ? "-" : "" ;
172+ const { minusSign, cleanedValue } = resolveValueSign (
173+ compressedArbitraryValue ,
174+ ! ! negativePrefix ,
175+ ) ;
190176 matchingPresets . push (
191- `${ modifiers } ${ minus } ${ classPrefix } -${ computedValue } ${ bang } ` ,
177+ `${ modifiers } ${ minusSign } ${ classPrefix } -${ cleanedValue } ${ bang } ` ,
192178 ) ;
193179 }
194180
195- // 4. Finally, check for spacing based presets e.g. `my-[2px]` → `my-2` (if spacing is 1px)
181+ // 4. Check for spacing based presets e.g. `my-[2px]` → `my-2`
196182 if ( supportsSpacing ( absBaseClass ) ) {
197- if ( debug ) console . log ( "#4" ) ;
198183 const spacingPresets = getThemePresetsFromPrefixes (
199184 theme ,
200185 new Set ( [ "--spacing" ] ) ,
201186 ) ;
202- let spacingValue = "0.25rem" ; // Fallback default
203- if ( spacingPresets . has ( "--spacing" ) ) {
204- spacingValue = spacingPresets . get ( "--spacing" ) as string ;
205- }
187+ const spacingValue = spacingPresets . get ( "--spacing" ) || "0.25rem" ;
206188
207- let computedValue = compressedArbitraryValue ;
208- let isNegative = ! ! negativePrefix ;
209- // Faster than `.startsWith("-")`
210- // eslint-disable-next-line unicorn/prefer-code-point
211- if ( compressedArbitraryValue . charCodeAt ( 0 ) === 45 ) {
212- computedValue = computedValue . slice ( 1 ) ;
213- isNegative = ! isNegative ;
214- }
189+ const { minusSign, cleanedValue } = resolveValueSign (
190+ compressedArbitraryValue ,
191+ ! ! negativePrefix ,
192+ ) ;
215193
216- // Convert spacingValue to px
217194 const spacingValueInPx = convertStringValueToPx ( spacingValue ) ;
218- // Convert computedValue to px
219- const valueInPx = convertStringValueToPx ( computedValue ) ;
220- if ( valueInPx === undefined || spacingValueInPx === undefined ) continue ;
221- const genericPresetValue = valueInPx / spacingValueInPx ;
222- // Only accepts integer values for now
223- if ( Number . isInteger ( genericPresetValue ) ) {
224- const minus = isNegative ? "-" : "" ;
225- matchingPresets . push (
226- ` ${ modifiers } ${ minus } ${ classPrefix } - ${ genericPresetValue } ${ bang } ` ,
227- ) ;
195+ const valueInPx = convertStringValueToPx ( cleanedValue ) ;
196+
197+ if ( valueInPx !== undefined && spacingValueInPx !== undefined ) {
198+ const genericPresetValue = valueInPx / spacingValueInPx ;
199+
200+ if ( Number . isInteger ( genericPresetValue ) ) {
201+ matchingPresets . push (
202+ ` ${ modifiers } ${ minusSign } ${ classPrefix } - ${ genericPresetValue } ${ bang } ` ,
203+ ) ;
204+ }
228205 }
229206 }
230207
231208 if ( matchingPresets . length === 0 ) continue ;
232209
233- // The location of the problematic classname
234210 const patchedLoc = generateLocForClassname (
235211 node ,
236212 targetClassName ,
@@ -248,32 +224,30 @@ const checkArbitraryClassnames = (
248224 arbitraryClass : targetClassName ,
249225 presetClasses : verbosePatches ,
250226 } ,
251- suggest : matchingPresets . map ( ( cls ) => {
252- return {
253- messageId : "fix:unnecessary-arbitrary" ,
254- data : {
255- arbitraryClass : targetClassName ,
256- presetClass : cls ,
257- } ,
258- fix : ( fixer ) => {
259- const clonedClassNames = [ ...classNames ] ;
260- // Patch the problematic classname
261- clonedClassNames [ index ] = cls ;
262- // Generates the "cleaned" attribute value
263- const patchedValue = joiner ( {
264- classNames : clonedClassNames ,
265- whitespaces,
266- headSpace,
267- tailSpace,
268- validator : ( candidate ) => candidate !== targetClassName ,
269- } ) ;
270- return fixer . replaceTextRange (
271- [ start , end ] ,
272- prefix + patchedValue + suffix ,
273- ) ;
274- } ,
275- } ;
276- } ) ,
227+ suggest : matchingPresets . map ( ( cls ) => ( {
228+ messageId : "fix:unnecessary-arbitrary" ,
229+ data : {
230+ arbitraryClass : targetClassName ,
231+ presetClass : cls ,
232+ } ,
233+ fix : ( fixer ) => {
234+ const clonedClassNames = [ ...classNames ] ;
235+ clonedClassNames [ index ] = cls ;
236+
237+ const patchedValue = joiner ( {
238+ classNames : clonedClassNames ,
239+ whitespaces,
240+ headSpace,
241+ tailSpace,
242+ validator : ( candidate ) => candidate !== targetClassName ,
243+ } ) ;
244+
245+ return fixer . replaceTextRange (
246+ [ start , end ] ,
247+ prefix + patchedValue + suffix ,
248+ ) ;
249+ } ,
250+ } ) ) ,
277251 } ) ;
278252 }
279253 }
0 commit comments