@antebudimir/eslint-plugin-vanilla-extract
Version:
ESLint plugin for enforcing best practices in vanilla-extract CSS styles, including CSS property ordering and additional linting rules.
72 lines (71 loc) • 4.08 kB
JavaScript
import { generateFixesForCSSOrder } from '../shared-utils/css-order-fixer.js';
import { createCSSPropertyPriorityMap } from '../shared-utils/css-property-priority-map.js';
/**
* Enforces a custom ordering of CSS properties based on user-defined groups.
*
* @param ruleContext The ESLint rule context used for reporting and fixing.
* @param cssPropertyInfoList An array of CSS property information objects to be ordered.
* @param userDefinedGroups Array of user-defined property groups for custom ordering.
* @param sortRemainingProperties Strategy for sorting properties not in user-defined groups
* ('alphabetical' or 'concentric'). Defaults to 'concentric'.
*
* This function compares CSS properties based on their group priority and position within
* those groups. Properties not part of user-defined groups are sorted according to the
* specified strategy. If an ordering violation is detected, an ESLint report is generated
* with a suggested fix.
*/
export const enforceCustomGroupOrder = (ruleContext, cssPropertyInfoList, userDefinedGroups = [], sortRemainingProperties) => {
if (cssPropertyInfoList.length > 1) {
const cssPropertyPriorityMap = createCSSPropertyPriorityMap(userDefinedGroups);
const compareProperties = (firstProperty, secondProperty) => {
const firstPropertyInfo = cssPropertyPriorityMap.get(firstProperty.name) || {
groupIndex: Infinity,
positionInGroup: Infinity,
inUserGroup: false,
};
const secondPropertyInfo = cssPropertyPriorityMap.get(secondProperty.name) || {
groupIndex: Infinity,
positionInGroup: Infinity,
inUserGroup: false,
};
if (firstPropertyInfo.inUserGroup !== secondPropertyInfo.inUserGroup) {
return firstPropertyInfo.inUserGroup ? -1 : 1;
}
if (firstPropertyInfo.inUserGroup) {
if (firstPropertyInfo.groupIndex !== secondPropertyInfo.groupIndex) {
return firstPropertyInfo.groupIndex - secondPropertyInfo.groupIndex;
}
return firstPropertyInfo.positionInGroup - secondPropertyInfo.positionInGroup;
}
// For properties not in user-defined groups
if (sortRemainingProperties === 'alphabetical') {
return firstProperty.name.localeCompare(secondProperty.name);
}
else {
return (firstPropertyInfo.groupIndex - secondPropertyInfo.groupIndex ||
firstPropertyInfo.positionInGroup - secondPropertyInfo.positionInGroup);
}
};
const sortedPropertyList = [...cssPropertyInfoList].sort(compareProperties);
// Find the first pair that violates the new ordering
const violatingProperty = cssPropertyInfoList
.slice(0, -1)
.find((currentProperty, index) => currentProperty.name !== sortedPropertyList[index]?.name);
if (violatingProperty) {
const indexInSorted = cssPropertyInfoList.indexOf(violatingProperty);
const sortedProperty = sortedPropertyList[indexInSorted];
// Defensive programming - sortedProperty will always exist and have a name because sortedPropertyList is derived from cssPropertyInfoList and cssPropertyInfoList exists and is non-empty.
// This fallback is theoretically unreachable in practice but included for type safety.
const nextPropertyName = sortedProperty?.name ?? '';
ruleContext.report({
node: violatingProperty.node,
messageId: 'incorrectOrder',
data: {
currentProperty: violatingProperty.name,
nextProperty: nextPropertyName,
},
fix: (fixer) => generateFixesForCSSOrder(fixer, ruleContext, cssPropertyInfoList, compareProperties, (propertyInfo) => propertyInfo.node),
});
}
}
};