mirror of
https://github.com/antebudimir/eslint-plugin-vanilla-extract.git
synced 2026-01-01 01:13:32 +00:00
feat 🥁: implement special ordering for fontFace APIs
- Ensure 'src' property always appears first - Sort remaining properties alphabetically - Handle both APIs correctly despite different argument structures - Handles font faces ordering the same in all 3 available CSS rules - Update documentation with fontFace ordering details
This commit is contained in:
parent
8916be7d16
commit
3e9bad1b02
12 changed files with 175 additions and 47 deletions
|
|
@ -1,23 +1,9 @@
|
|||
import type { Rule } from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import { comparePropertiesAlphabetically } from '../shared-utils/alphabetical-property-comparator.js';
|
||||
import { generateFixesForCSSOrder } from '../shared-utils/css-order-fixer.js';
|
||||
import { getPropertyName } from '../shared-utils/property-separator.js';
|
||||
|
||||
/**
|
||||
* Compares two CSS properties alphabetically.
|
||||
* @param firstProperty The first property to compare.
|
||||
* @param secondProperty The second property to compare.
|
||||
* @returns A number indicating the relative order of the properties (-1, 0, or 1).
|
||||
*/
|
||||
const comparePropertiesAlphabetically = (
|
||||
firstProperty: TSESTree.Property,
|
||||
secondProperty: TSESTree.Property,
|
||||
): number => {
|
||||
const firstName = getPropertyName(firstProperty);
|
||||
const secondName = getPropertyName(secondProperty);
|
||||
return firstName.localeCompare(secondName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reports an ordering issue to ESLint and generates fixes.
|
||||
* @param ruleContext The ESLint rule context.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ const alphabeticalOrderRule: Rule.RuleModule = {
|
|||
fixable: 'code',
|
||||
schema: [],
|
||||
messages: {
|
||||
alphabeticalOrder: "Property '{{next}}' should come before '{{current}}' in alphabetical order.",
|
||||
alphabeticalOrder: "Property '{{nextProperty}}' should come before '{{currentProperty}}' in alphabetical order.",
|
||||
fontFaceOrder:
|
||||
"Properties in fontFace should be ordered with 'src' first, followed by other properties in alphabetical order. Property '{{nextProperty}}' should come before '{{currentProperty}}'.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ const reportOrderingIssue = (
|
|||
node: nextProperty.node as Rule.Node,
|
||||
messageId: 'incorrectOrder',
|
||||
data: {
|
||||
next: nextProperty.name,
|
||||
current: currentProperty.name,
|
||||
nextProperty: nextProperty.name,
|
||||
currentProperty: currentProperty.name,
|
||||
},
|
||||
fix: (fixer) =>
|
||||
generateFixesForCSSOrder(
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@ const concentricOrderRule: Rule.RuleModule = {
|
|||
description: 'enforce concentric CSS property ordering in vanilla-extract styles',
|
||||
category: 'Stylistic Issues',
|
||||
recommended: true,
|
||||
url: 'https://rhodesmill.org/brandon/2011/concentric-css/',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [],
|
||||
messages: {
|
||||
incorrectOrder: "Property '{{next}}' should come before '{{current}}' according to concentric CSS ordering.",
|
||||
incorrectOrder:
|
||||
"Property '{{nextProperty}}' should come before '{{currentProperty}}' according to concentric CSS ordering.",
|
||||
fontFaceOrder:
|
||||
"Properties in fontFace should be ordered with 'src' first, followed by other properties in alphabetical order. Property '{{nextProperty}}' should come before '{{currentProperty}}'.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ const customGroupOrderRule: Rule.RuleModule = {
|
|||
messages: {
|
||||
incorrectOrder:
|
||||
"Property '{{nextProperty}}' should come before '{{currentProperty}}' according to custom CSS group ordering.",
|
||||
fontFaceOrder:
|
||||
"Properties in fontFace should be ordered with 'src' first, followed by other properties in alphabetical order. Property '{{nextProperty}}' should come before '{{currentProperty}}'.",
|
||||
},
|
||||
},
|
||||
create(ruleContext: Rule.RuleContext) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { getPropertyName } from './property-separator.js';
|
||||
import type { TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
/**
|
||||
* Compares two CSS properties alphabetically.
|
||||
* @param firstProperty The first property to compare.
|
||||
* @param secondProperty The second property to compare.
|
||||
* @returns A number indicating the relative order of the properties (-1, 0, or 1).
|
||||
*/
|
||||
export const comparePropertiesAlphabetically = (
|
||||
firstProperty: TSESTree.Property,
|
||||
secondProperty: TSESTree.Property,
|
||||
): number => {
|
||||
const firstName = getPropertyName(firstProperty);
|
||||
const secondName = getPropertyName(secondProperty);
|
||||
|
||||
// Special handling for 'src' property - it should always come first (relates to font face APIs only)
|
||||
if (firstName === 'src') {
|
||||
return -1;
|
||||
}
|
||||
if (secondName === 'src') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return firstName.localeCompare(secondName);
|
||||
};
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import type { Rule } from 'eslint';
|
||||
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
|
||||
import { generateFixesForCSSOrder } from '../shared-utils/css-order-fixer.js';
|
||||
import { getPropertyName, separateProperties } from '../shared-utils/property-separator.js';
|
||||
import { comparePropertiesAlphabetically } from './alphabetical-property-comparator.js';
|
||||
|
||||
/**
|
||||
* Processes a font face declaration to enforce property ordering:
|
||||
* 'src' first, then other properties alphabetically.
|
||||
*
|
||||
* @param ruleContext The ESLint rule context for reporting and fixing issues.
|
||||
* @param fontFaceObject The object expression representing the font face declaration.
|
||||
*/
|
||||
export const enforceFontFaceOrder = (
|
||||
ruleContext: Rule.RuleContext,
|
||||
fontFaceObject: TSESTree.ObjectExpression,
|
||||
): void => {
|
||||
if (!fontFaceObject || fontFaceObject.type !== AST_NODE_TYPES.ObjectExpression) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { regularProperties } = separateProperties(fontFaceObject.properties);
|
||||
|
||||
if (regularProperties.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create pairs of consecutive properties
|
||||
const propertyPairs = regularProperties.slice(0, -1).map((currentProperty, index) => ({
|
||||
currentProperty,
|
||||
nextProperty: regularProperties[index + 1] as TSESTree.Property,
|
||||
}));
|
||||
|
||||
const violatingPair = propertyPairs.find(
|
||||
({ currentProperty, nextProperty }) => comparePropertiesAlphabetically(currentProperty, nextProperty) > 0,
|
||||
);
|
||||
|
||||
if (violatingPair) {
|
||||
const nextPropertyName = getPropertyName(violatingPair.nextProperty);
|
||||
const currentPropertyName = getPropertyName(violatingPair.currentProperty);
|
||||
|
||||
ruleContext.report({
|
||||
node: violatingPair.nextProperty as Rule.Node,
|
||||
messageId: 'fontFaceOrder',
|
||||
data: {
|
||||
nextProperty: nextPropertyName,
|
||||
currentProperty: currentPropertyName,
|
||||
},
|
||||
fix: (fixer) =>
|
||||
generateFixesForCSSOrder(
|
||||
fixer,
|
||||
ruleContext,
|
||||
regularProperties,
|
||||
comparePropertiesAlphabetically,
|
||||
(property) => property as Rule.Node,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import { enforceConcentricCSSOrderInRecipe } from '../concentric-order/recipe-or
|
|||
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 { processStyleNode } from './style-node-processor.js';
|
||||
|
||||
/**
|
||||
|
|
@ -58,6 +59,26 @@ export const createNodeVisitors = (
|
|||
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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle style-related functions
|
||||
if (
|
||||
['createThemeContract', 'createVar', 'createTheme', 'keyframes', 'style', 'styleVariants'].includes(
|
||||
|
|
@ -65,8 +86,8 @@ export const createNodeVisitors = (
|
|||
)
|
||||
) {
|
||||
if (node.arguments.length > 0) {
|
||||
const styleArg = node.arguments[0];
|
||||
processStyleNode(ruleContext, styleArg as TSESTree.Node, processProperty);
|
||||
const styleArguments = node.arguments[0];
|
||||
processStyleNode(ruleContext, styleArguments as TSESTree.Node, processProperty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,8 +96,8 @@ export const createNodeVisitors = (
|
|||
(node.callee.name === 'globalKeyframes' || node.callee.name === 'globalStyle') &&
|
||||
node.arguments.length >= 2
|
||||
) {
|
||||
const styleArg = node.arguments[1];
|
||||
processStyleNode(ruleContext, styleArg as TSESTree.Node, processProperty);
|
||||
const styleArguments = node.arguments[1];
|
||||
processStyleNode(ruleContext, styleArguments as TSESTree.Node, processProperty);
|
||||
}
|
||||
|
||||
// Handle recipe function
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue