@antebudimir/eslint-plugin-vanilla-extract
Version:
Comprehensive ESLint plugin for vanilla-extract with CSS property ordering, style validation, and best practices enforcement. Supports alphabetical, concentric and custom CSS ordering, auto-fixing, and zero-runtime safety.
82 lines (81 loc) • 3.79 kB
JavaScript
import * as path from 'path';
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 { ThemeContractAnalyzer } from './theme-contract-analyzer.js';
import { processThemeTokensInStyleObject } from './theme-token-processor.js';
/**
* Creates ESLint rule visitors for the prefer-theme-tokens rule
*/
export const createThemeTokenVisitors = (context, options) => {
const tracker = new ReferenceTracker();
const trackingVisitor = createReferenceTrackingVisitor(tracker);
// Initialize the theme contract analyzer
const analyzer = new ThemeContractAnalyzer();
// Set rem base if provided
if (options.remBase) {
analyzer.setRemBase(options.remBase);
}
// Load theme contracts if provided
const themeContracts = options.themeContracts || [];
if (themeContracts.length > 0) {
// Use getCwd() to get project root, or fallback to linted file's directory
const baseDir = context.getCwd ? context.getCwd() : path.dirname(context.filename || context.getFilename());
themeContracts.forEach((contractPath) => {
analyzer.loadThemeContract(contractPath, baseDir);
});
}
const process = (context, object) => processThemeTokensInStyleObject(context, object, options, analyzer);
return {
...trackingVisitor,
// Call the tracking visitor's ImportDeclaration handler
ImportDeclaration(node) {
if (trackingVisitor.ImportDeclaration) {
trackingVisitor.ImportDeclaration(node);
}
},
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]);
}
break;
case 'globalFontFace':
if (node.arguments.length > 1 && node.arguments[1]?.type === AST_NODE_TYPES.ObjectExpression) {
process(context, node.arguments[1]);
}
break;
case 'style':
case 'styleVariants':
case 'keyframes':
if (node.arguments.length > 0) {
processStyleNode(context, node.arguments[0], (context, object) => process(context, object));
}
break;
case 'globalStyle':
if (node.arguments.length > 1 && node.arguments[1]?.type === AST_NODE_TYPES.ObjectExpression) {
process(context, node.arguments[1]);
}
break;
case 'recipe':
case 'compoundVariant':
if (node.arguments.length > 0 && node.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression) {
processRecipeProperties(context, node.arguments[0], (context, object) => process(context, object));
}
break;
default:
break;
}
},
};
};