mirror of
https://github.com/antebudimir/eslint-plugin-vanilla-extract.git
synced 2026-01-02 17:43:31 +00:00
feat 🥁: add prefer-logical-properties rule for i18n-friendly styles
- Add new rule `prefer-logical-properties` that enforces logical CSS properties over physical directional properties - Detects 140+ physical property mappings across margin, padding, border, inset, size, overflow, and scroll properties - Supports value-based detection for `text-align`, `float`, `clear`, and `resize` properties - Provides automatic fixes for all detected violations - Preserves original formatting (camelCase/kebab-case and quote style) - Configurable allowlist via `allow` option to skip specific properties - Comprehensive test coverage
This commit is contained in:
parent
69dd109311
commit
5b0bcf17c7
10 changed files with 1201 additions and 3 deletions
|
|
@ -0,0 +1,76 @@
|
|||
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;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue