mirror of
https://github.com/antebudimir/eslint-plugin-vanilla-extract.git
synced 2025-12-31 08:53:33 +00:00
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.
This commit is contained in:
parent
35875fbb31
commit
02576d923c
15 changed files with 1942 additions and 212 deletions
|
|
@ -7,25 +7,20 @@ import { enforceConcentricCSSOrderInStyleObject } from '../concentric-order/styl
|
|||
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.
|
||||
* 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.
|
||||
*
|
||||
* This function sets up visitors for the following cases:
|
||||
* 1. The fontFace and globalFontFace functions.
|
||||
* 2. Style-related functions: 'keyframes', 'style', 'styleVariants'.
|
||||
* 3. The 'globalStyle' and 'globalKeyframes' function
|
||||
* 4. The 'recipe' function
|
||||
*
|
||||
* Each visitor applies the appropriate ordering strategy to the style objects in these function calls.
|
||||
*/
|
||||
export const createNodeVisitors = (
|
||||
ruleContext: Rule.RuleContext,
|
||||
|
|
@ -33,88 +28,157 @@ export const createNodeVisitors = (
|
|||
userDefinedGroupOrder?: string[],
|
||||
sortRemainingProperties?: SortRemainingProperties,
|
||||
): Rule.RuleListener => {
|
||||
// Select the appropriate property processing function based on the ordering strategy
|
||||
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;
|
||||
}
|
||||
})();
|
||||
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 fontFaceFunctionArgumentIndexMap = {
|
||||
fontFace: 0, // First argument (index 0)
|
||||
globalFontFace: 1, // Second argument (index 1)
|
||||
};
|
||||
|
||||
// Handle font face functions with special ordering
|
||||
if (
|
||||
node.callee.name in fontFaceFunctionArgumentIndexMap &&
|
||||
node.arguments.length >
|
||||
fontFaceFunctionArgumentIndexMap[node.callee.name as keyof typeof fontFaceFunctionArgumentIndexMap]
|
||||
) {
|
||||
const argumentIndex =
|
||||
fontFaceFunctionArgumentIndexMap[node.callee.name as keyof typeof fontFaceFunctionArgumentIndexMap];
|
||||
const styleArguments = node.arguments[argumentIndex];
|
||||
|
||||
enforceFontFaceOrder(ruleContext, styleArguments as TSESTree.ObjectExpression);
|
||||
const functionName = node.callee.name;
|
||||
|
||||
// Check if this function is tracked as a vanilla-extract function
|
||||
if (!tracker.isTrackedFunction(functionName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle style-related functions
|
||||
if (['keyframes', 'style', 'styleVariants'].includes(node.callee.name)) {
|
||||
if (node.arguments.length > 0) {
|
||||
const styleArguments = node.arguments[0];
|
||||
processStyleNode(ruleContext, styleArguments as TSESTree.Node, processProperty);
|
||||
}
|
||||
const originalName = tracker.getOriginalName(functionName);
|
||||
if (!originalName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle global functions
|
||||
if (
|
||||
(node.callee.name === 'globalKeyframes' || node.callee.name === 'globalStyle') &&
|
||||
node.arguments.length >= 2
|
||||
) {
|
||||
const styleArguments = node.arguments[1];
|
||||
processStyleNode(ruleContext, styleArguments as TSESTree.Node, processProperty);
|
||||
}
|
||||
// Handle different function types based on their original imported name
|
||||
switch (originalName) {
|
||||
case 'fontFace':
|
||||
processFontFaceOrdering(ruleContext, node as TSESTree.CallExpression, 0);
|
||||
break;
|
||||
|
||||
// Handle recipe function
|
||||
if (node.callee.name === 'recipe') {
|
||||
switch (orderingStrategy) {
|
||||
case 'alphabetical':
|
||||
enforceAlphabeticalCSSOrderInRecipe(node as TSESTree.CallExpression, ruleContext);
|
||||
break;
|
||||
case 'concentric':
|
||||
enforceConcentricCSSOrderInRecipe(ruleContext, node as TSESTree.CallExpression);
|
||||
break;
|
||||
case 'userDefinedGroupOrder':
|
||||
if (userDefinedGroupOrder) {
|
||||
enforceUserDefinedGroupOrderInRecipe(
|
||||
ruleContext,
|
||||
node as TSESTree.CallExpression,
|
||||
userDefinedGroupOrder,
|
||||
sortRemainingProperties,
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue