eslint-plugin-vanilla-extract/src/css-rules/custom-order/property-order-enforcer.ts

91 lines
3.5 KiB
TypeScript

import type { Rule } from 'eslint';
import { generateFixesForCSSOrder } from '../shared-utils/css-order-fixer.js';
import { createCSSPropertyPriorityMap } from '../shared-utils/css-property-priority-map.js';
import type { CSSPropertyInfo } from '../concentric-order/types.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: Rule.RuleContext,
cssPropertyInfoList: CSSPropertyInfo[],
userDefinedGroups: string[] = [],
sortRemainingProperties?: 'alphabetical' | 'concentric',
): void => {
if (cssPropertyInfoList.length <= 1) {
return;
}
const cssPropertyPriorityMap = createCSSPropertyPriorityMap(userDefinedGroups);
const compareProperties = (firstProperty: CSSPropertyInfo, secondProperty: CSSPropertyInfo) => {
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) {
ruleContext.report({
node: violatingProperty.node as Rule.Node,
messageId: 'incorrectOrder',
data: {
currentProperty: violatingProperty.name,
nextProperty: sortedPropertyList[cssPropertyInfoList.indexOf(violatingProperty)]?.name || '',
},
fix: (fixer) =>
generateFixesForCSSOrder(
fixer,
ruleContext,
cssPropertyInfoList,
compareProperties,
(propertyInfo) => propertyInfo.node as Rule.Node,
),
});
}
};