eslint-plugin-vanilla-extract/src/css-rules/shared-utils/order-strategy-visitor-creator.ts
Seongmin Choi 02576d923c
feat 🥁: add wrapper function support with reference tracking
- add reference tracking for wrapper functions in vanilla-extract style objects
- implement ReferenceTracker class for detecting vanilla-extract imports
- add createReferenceBasedNodeVisitors for automatic function detection
- support wrapper functions with parameter mapping enable all lint rules to work with custom wrapper functions

This commit introduces robust reference tracking and wrapper function support, enabling all lint rules to work seamlessly with custom vanilla-extract style patterns while preserving compatibility with existing usage and improving rule extensibility.
2025-06-25 16:51:36 +00:00

184 lines
6.4 KiB
TypeScript

import type { Rule } from 'eslint';
import { TSESTree } from '@typescript-eslint/utils';
import { enforceAlphabeticalCSSOrderInRecipe } from '../alphabetical-order/recipe-order-enforcer.js';
import { enforceAlphabeticalCSSOrderInStyleObject } from '../alphabetical-order/style-object-processor.js';
import { enforceConcentricCSSOrderInRecipe } from '../concentric-order/recipe-order-enforcer.js';
import { enforceConcentricCSSOrderInStyleObject } from '../concentric-order/style-object-processor.js';
import { enforceUserDefinedGroupOrderInRecipe } from '../custom-order/recipe-order-enforcer.js';
import { enforceUserDefinedGroupOrderInStyleObject } from '../custom-order/style-object-processor.js';
import { enforceFontFaceOrder } from './font-face-property-order-enforcer.js';
import { ReferenceTracker, createReferenceTrackingVisitor } from './reference-tracker.js';
import { processStyleNode } from './style-node-processor.js';
import type { SortRemainingProperties } from '../concentric-order/types.js';
import type { OrderingStrategy } from '../types.js';
/**
* Creates an ESLint rule listener with visitors for style-related function calls using reference tracking.
* This automatically detects vanilla-extract functions based on their import statements.
*
* @param ruleContext The ESLint rule context.
* @param orderingStrategy The strategy to use for ordering CSS properties ('alphabetical', 'concentric', or 'userDefinedGroupOrder').
* @param userDefinedGroupOrder An optional array of property groups for the 'userDefinedGroupOrder' strategy.
* @param sortRemainingProperties An optional strategy for sorting properties not in user-defined groups.
* @returns An object with visitor functions for the ESLint rule.
*/
export const createNodeVisitors = (
ruleContext: Rule.RuleContext,
orderingStrategy: OrderingStrategy,
userDefinedGroupOrder?: string[],
sortRemainingProperties?: SortRemainingProperties,
): Rule.RuleListener => {
const tracker = new ReferenceTracker();
const trackingVisitor = createReferenceTrackingVisitor(tracker);
return {
// Include the import/variable tracking visitors
...trackingVisitor,
CallExpression(node) {
if (node.callee.type !== 'Identifier') {
return;
}
const functionName = node.callee.name;
// Check if this function is tracked as a vanilla-extract function
if (!tracker.isTrackedFunction(functionName)) {
return;
}
const originalName = tracker.getOriginalName(functionName);
if (!originalName) {
return;
}
// Handle different function types based on their original imported name
switch (originalName) {
case 'fontFace':
processFontFaceOrdering(ruleContext, node as TSESTree.CallExpression, 0);
break;
case 'globalFontFace':
processFontFaceOrdering(ruleContext, node as TSESTree.CallExpression, 1);
break;
case 'style':
case 'styleVariants':
case 'keyframes':
// Check if this is a wrapper function
const wrapperInfo = tracker.getWrapperInfo(functionName);
const argumentIndex = wrapperInfo?.parameterMapping ?? 0;
processStyleOrdering(
ruleContext,
node as TSESTree.CallExpression,
orderingStrategy,
userDefinedGroupOrder,
sortRemainingProperties,
argumentIndex,
);
break;
case 'globalStyle':
case 'globalKeyframes':
processStyleOrdering(
ruleContext,
node as TSESTree.CallExpression,
orderingStrategy,
userDefinedGroupOrder,
sortRemainingProperties,
1,
);
break;
case 'recipe':
processRecipeOrdering(
ruleContext,
node as TSESTree.CallExpression,
orderingStrategy,
userDefinedGroupOrder,
sortRemainingProperties,
);
break;
}
},
};
};
/**
* Helper function to process style ordering for style-related functions
*/
const processStyleOrdering = (
ruleContext: Rule.RuleContext,
node: TSESTree.CallExpression,
orderingStrategy: OrderingStrategy,
userDefinedGroupOrder?: string[],
sortRemainingProperties?: SortRemainingProperties,
argumentIndex: number = 0,
) => {
if (node.arguments.length > argumentIndex) {
const processProperty = (() => {
switch (orderingStrategy) {
case 'alphabetical':
return enforceAlphabeticalCSSOrderInStyleObject;
case 'concentric':
return enforceConcentricCSSOrderInStyleObject;
case 'userDefinedGroupOrder':
if (!userDefinedGroupOrder || userDefinedGroupOrder.length === 0) {
return enforceAlphabeticalCSSOrderInStyleObject;
}
return (ruleContext: Rule.RuleContext, node: TSESTree.ObjectExpression) =>
enforceUserDefinedGroupOrderInStyleObject(
ruleContext,
node,
userDefinedGroupOrder,
sortRemainingProperties,
);
default:
return enforceAlphabeticalCSSOrderInStyleObject;
}
})();
processStyleNode(ruleContext, node.arguments[argumentIndex] as TSESTree.ObjectExpression, processProperty);
}
};
/**
* Helper function to process font face ordering
*/
const processFontFaceOrdering = (
ruleContext: Rule.RuleContext,
node: TSESTree.CallExpression,
argumentIndex: number,
) => {
if (node.arguments.length > argumentIndex) {
enforceFontFaceOrder(ruleContext, node.arguments[argumentIndex] as TSESTree.ObjectExpression);
}
};
/**
* Helper function to process recipe ordering
*/
const processRecipeOrdering = (
ruleContext: Rule.RuleContext,
node: TSESTree.CallExpression,
orderingStrategy: OrderingStrategy,
userDefinedGroupOrder?: string[],
sortRemainingProperties?: SortRemainingProperties,
) => {
if (node.arguments.length > 0) {
switch (orderingStrategy) {
case 'alphabetical':
enforceAlphabeticalCSSOrderInRecipe(node, ruleContext);
break;
case 'concentric':
enforceConcentricCSSOrderInRecipe(ruleContext, node);
break;
case 'userDefinedGroupOrder':
if (userDefinedGroupOrder) {
enforceUserDefinedGroupOrderInRecipe(ruleContext, node, userDefinedGroupOrder, sortRemainingProperties);
}
break;
}
}
};