feat 🥁: initialize project with complete codebase

This commit is contained in:
Ante Budimir 2025-03-04 20:16:50 +02:00
commit d569dea1fb
35 changed files with 4413 additions and 0 deletions

View file

@ -0,0 +1,91 @@
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,
),
});
}
};

View file

@ -0,0 +1,42 @@
import type { Rule } from 'eslint';
import { TSESTree } from '@typescript-eslint/utils';
import { processRecipeProperties } from '../shared-utils/recipe-property-processor.js';
import { processStyleNode } from '../shared-utils/style-node-processor.js';
import { enforceUserDefinedGroupOrderInStyleObject } from './style-object-processor.js';
/**
* Enforces custom group ordering of CSS properties within a recipe function call.
*
* @param ruleContext The ESLint rule context for reporting and fixing issues.
* @param callExpression The CallExpression node representing the recipe function call.
* @param userDefinedGroups An array of property groups in the desired order.
*
* This function does the following:
* 1. Validates that the first argument of the recipe function is an ObjectExpression.
* 2. Processes the recipe object's properties if valid.
* 3. Applies custom group ordering to CSS properties in relevant properties (e.g., 'base', 'variants').
* 4. Processes nested selectors and style objects recursively.
*/
export const enforceUserDefinedGroupOrderInRecipe = (
ruleContext: Rule.RuleContext,
callExpression: TSESTree.CallExpression,
userDefinedGroups: string[],
sortRemainingPropertiesMethod?: 'alphabetical' | 'concentric',
): void => {
if (!callExpression.arguments[0] || callExpression.arguments[0].type !== 'ObjectExpression') {
return;
}
const recipeObjectExpression = callExpression.arguments[0];
processRecipeProperties(ruleContext, recipeObjectExpression, (currentContext, styleObject) =>
processStyleNode(currentContext, styleObject, (styleContext, styleObjectNode) =>
enforceUserDefinedGroupOrderInStyleObject(
styleContext,
styleObjectNode,
userDefinedGroups,
sortRemainingPropertiesMethod,
),
),
);
};

View file

@ -0,0 +1,55 @@
import type { Rule } from 'eslint';
import { availableGroups } from '../concentric-order/concentric-groups.js';
import { createNodeVisitors } from '../shared-utils/order-strategy-visitor-creator.js';
interface CustomGroupRuleConfiguration {
groupOrder?: string[];
sortRemainingProperties: 'alphabetical' | 'concentric';
}
const customGroupOrderRule: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce custom group CSS property ordering in vanilla-extract styles',
category: 'Stylistic Issues',
recommended: true,
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
groupOrder: {
type: 'array',
items: {
enum: availableGroups,
},
},
sortRemainingProperties: {
enum: ['alphabetical', 'concentric'],
},
},
additionalProperties: false,
},
],
messages: {
incorrectOrder:
"Property '{{nextProperty}}' should come before '{{currentProperty}}' according to custom CSS group ordering.",
},
},
create(ruleContext: Rule.RuleContext) {
const ruleConfiguration = ruleContext.options[0] as CustomGroupRuleConfiguration;
const userDefinedGroupOrder = ruleConfiguration?.groupOrder ?? [];
const sortRemainingPropertiesMethod = ruleConfiguration?.sortRemainingProperties ?? 'alphabetical';
return createNodeVisitors(
ruleContext,
'userDefinedGroupOrder',
userDefinedGroupOrder,
sortRemainingPropertiesMethod,
);
},
};
export default customGroupOrderRule;

View file

@ -0,0 +1,79 @@
import type { Rule } from 'eslint';
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
import { concentricGroups } from '../concentric-order/concentric-groups.js';
import { createCSSPropertyPriorityMap } from '../shared-utils/css-property-priority-map.js';
import { isSelectorsObject, processNestedSelectors } from '../shared-utils/nested-selectors-processor.js';
import { getPropertyName, separateProperties } from '../shared-utils/property-separator.js';
import { enforceCustomGroupOrder } from './property-order-enforcer.js';
import type { CSSPropertyInfo } from '../concentric-order/types.js';
/**
* Enforces a custom ordering of CSS properties based on user-defined groups in a given style object.
*
* @param context The ESLint rule context for reporting and fixing issues.
* @param styleObject The ObjectExpression node representing the style object to be processed.
* @param userDefinedGroups An array of property groups in the desired order.
* @param sortRemainingPropertiesMethod Strategy for sorting properties not in user-defined groups ('alphabetical' or 'concentric'). Defaults to 'concentric'.
*
* This function:
* 1. Validates the input styleObject.
* 2. Handles 'selectors' objects separately, processing their nested style objects.
* 3. Creates a priority map based on user-defined groups.
* 4. Processes regular properties, creating a list of CSSPropertyInfo objects.
* 5. Enforces custom group ordering on the properties.
* 6. Recursively processes nested selectors and style objects.
*/
export const enforceUserDefinedGroupOrderInStyleObject = (
ruleContext: Rule.RuleContext,
styleObject: TSESTree.ObjectExpression,
userDefinedGroups: string[],
sortRemainingPropertiesMethod: 'alphabetical' | 'concentric' = 'concentric',
): void => {
if (!styleObject || styleObject.type !== AST_NODE_TYPES.ObjectExpression) {
return;
}
if (isSelectorsObject(styleObject)) {
styleObject.properties.forEach((property) => {
if (property.type === AST_NODE_TYPES.Property && property.value.type === AST_NODE_TYPES.ObjectExpression) {
enforceUserDefinedGroupOrderInStyleObject(
ruleContext,
property.value,
userDefinedGroups,
sortRemainingPropertiesMethod,
);
}
});
return;
}
const cssPropertyPriorityMap = createCSSPropertyPriorityMap(userDefinedGroups);
const { regularProperties } = separateProperties(styleObject.properties);
const cssPropertyInfoList: CSSPropertyInfo[] = regularProperties.map((property) => {
const propertyName = getPropertyName(property);
const propertyInfo = cssPropertyPriorityMap.get(propertyName);
const group =
userDefinedGroups.find((groupName) => concentricGroups[groupName]?.includes(propertyName)) || 'remaining';
return {
name: propertyName,
node: property,
priority: propertyInfo?.groupIndex ?? Number.MAX_SAFE_INTEGER,
positionInGroup: propertyInfo?.positionInGroup ?? Number.MAX_SAFE_INTEGER,
group,
inUserGroup: propertyInfo?.inUserGroup ?? false,
};
});
enforceCustomGroupOrder(ruleContext, cssPropertyInfoList, userDefinedGroups, sortRemainingPropertiesMethod);
processNestedSelectors(ruleContext, styleObject, (nestedContext, nestedNode) =>
enforceUserDefinedGroupOrderInStyleObject(
nestedContext,
nestedNode,
userDefinedGroups,
sortRemainingPropertiesMethod,
),
);
};