mirror of
https://github.com/antebudimir/eslint-plugin-vanilla-extract.git
synced 2026-01-02 17:43:31 +00:00
77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
|
|
import type { Rule } from 'eslint';
|
||
|
|
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
|
||
|
|
import { processRecipeProperties } from '../shared-utils/recipe-property-processor.js';
|
||
|
|
import { ReferenceTracker, createReferenceTrackingVisitor } from '../shared-utils/reference-tracker.js';
|
||
|
|
import { processStyleNode } from '../shared-utils/style-node-processor.js';
|
||
|
|
import { processLogicalPropertiesInStyleObject, type LogicalPropertiesOptions } from './logical-properties-processor.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Creates ESLint rule visitors for detecting and reporting physical CSS properties
|
||
|
|
* in vanilla-extract style objects.
|
||
|
|
*
|
||
|
|
* - Tracks calls to vanilla-extract APIs (style, recipe, keyframes, globalStyle, fontFace, etc.)
|
||
|
|
* - Detects physical property names and directional values
|
||
|
|
* - Respects the `allow` option for allowlisting properties
|
||
|
|
* - Provides auto-fixes for unambiguous conversions
|
||
|
|
*
|
||
|
|
* @param context ESLint rule context used to read options and report diagnostics
|
||
|
|
* @returns Rule listener that inspects vanilla-extract call expressions and processes style objects
|
||
|
|
*/
|
||
|
|
export const createLogicalPropertiesVisitors = (context: Rule.RuleContext): Rule.RuleListener => {
|
||
|
|
const tracker = new ReferenceTracker();
|
||
|
|
const trackingVisitor = createReferenceTrackingVisitor(tracker);
|
||
|
|
|
||
|
|
const options = (context.options?.[0] as LogicalPropertiesOptions | undefined) || {};
|
||
|
|
const allowSet = new Set((options.allow ?? []).map((prop) => prop));
|
||
|
|
|
||
|
|
const process = (context: Rule.RuleContext, object: TSESTree.ObjectExpression) =>
|
||
|
|
processLogicalPropertiesInStyleObject(context, object, allowSet);
|
||
|
|
|
||
|
|
return {
|
||
|
|
...trackingVisitor,
|
||
|
|
|
||
|
|
CallExpression(node) {
|
||
|
|
if (node.callee.type !== AST_NODE_TYPES.Identifier) return;
|
||
|
|
|
||
|
|
const functionName = node.callee.name;
|
||
|
|
if (!tracker.isTrackedFunction(functionName)) return;
|
||
|
|
|
||
|
|
const originalName = tracker.getOriginalName(functionName);
|
||
|
|
if (!originalName) return;
|
||
|
|
|
||
|
|
switch (originalName) {
|
||
|
|
case 'fontFace':
|
||
|
|
if (node.arguments.length > 0 && node.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression) {
|
||
|
|
process(context, node.arguments[0] as TSESTree.ObjectExpression);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'globalFontFace':
|
||
|
|
if (node.arguments.length > 1 && node.arguments[1]?.type === AST_NODE_TYPES.ObjectExpression) {
|
||
|
|
process(context, node.arguments[1] as TSESTree.ObjectExpression);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'style':
|
||
|
|
case 'styleVariants':
|
||
|
|
case 'keyframes':
|
||
|
|
if (node.arguments.length > 0) {
|
||
|
|
processStyleNode(context, node.arguments[0] as TSESTree.Node, (context, object) => process(context, object));
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'globalStyle':
|
||
|
|
case 'globalKeyframes':
|
||
|
|
if (node.arguments.length >= 2) {
|
||
|
|
processStyleNode(context, node.arguments[1] as TSESTree.Node, (context, object) => process(context, object));
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'recipe':
|
||
|
|
if (node.arguments.length > 0 && node.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression) {
|
||
|
|
processRecipeProperties(context, node.arguments[0] as TSESTree.ObjectExpression, (context, object) =>
|
||
|
|
process(context, object),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
};
|
||
|
|
};
|