Skip to content

Commit 7c5e73a

Browse files
refactor
1 parent 5c46b0f commit 7c5e73a

1 file changed

Lines changed: 73 additions & 99 deletions

File tree

src/rules/no-unnecessary-arbitrary-value.ts

Lines changed: 73 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,23 @@ export const createRule = RuleCreator(urlCreator);
6363
const 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+
6676
const 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

Comments
 (0)