feat 🥁: add prefer-theme-tokens rule
Some checks failed
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled

- Enforce theme tokens over hard-coded values in vanilla-extract styles (colors, spacing, font sizes, border radius/widths, shadows, z-index, opacity, font weights, transitions)
- Provide token suggestions from configured theme contracts; optional auto-fix for unambiguous replacements
This commit is contained in:
Ante Budimir 2025-11-13 21:26:44 +02:00
parent d5eae5dfc8
commit 1d88c12e3d
16 changed files with 3201 additions and 21 deletions

View file

@ -0,0 +1,126 @@
import type { Rule } from 'eslint';
import { createThemeTokenVisitors } from './theme-token-visitor-creator.js';
import type { ThemeTokenOptions } from './theme-token-processor.js';
const rule: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description:
'require theme tokens instead of hard-coded values for colors, spacing, font sizes, and border radius',
recommended: false,
},
fixable: 'code',
// Suggestions are reported from helper modules, so static analysis in this file cant detect them; disable the false positive.
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
hasSuggestions: true,
schema: [
{
type: 'object',
properties: {
themeContracts: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of theme contract file paths to analyze for intelligent token suggestions',
},
checkColors: {
type: 'boolean',
description: 'Check for hard-coded color values',
default: true,
},
checkSpacing: {
type: 'boolean',
description: 'Check for hard-coded spacing values',
default: true,
},
checkFontSizes: {
type: 'boolean',
description: 'Check for hard-coded font size values',
default: true,
},
checkBorderRadius: {
type: 'boolean',
description: 'Check for hard-coded border radius values',
default: true,
},
checkBorderWidths: {
type: 'boolean',
description: 'Check for hard-coded border width values',
default: true,
},
checkShadows: {
type: 'boolean',
description: 'Check for hard-coded shadow values',
default: true,
},
checkZIndex: {
type: 'boolean',
description: 'Check for hard-coded z-index values',
default: true,
},
checkOpacity: {
type: 'boolean',
description: 'Check for hard-coded opacity values',
default: true,
},
checkFontWeights: {
type: 'boolean',
description: 'Check for hard-coded font weight values',
default: true,
},
checkTransitions: {
type: 'boolean',
description: 'Check for hard-coded transition and animation values',
default: true,
},
allowedValues: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of values that are allowed (e.g., "0", "auto", "100%")',
},
allowedProperties: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of CSS properties to skip checking (supports both camelCase and kebab-case)',
},
autoFix: {
type: 'boolean',
description: 'Enable auto-fix for unambiguous token replacements',
default: false,
},
remBase: {
type: 'number',
description: 'Base font size for rem() calculations (default: 16)',
default: 16,
},
checkHelperFunctions: {
type: 'boolean',
description: 'Check helper function calls like rem(48) and suggest theme tokens (default: false)',
default: false,
},
},
additionalProperties: false,
},
],
messages: {
hardCodedValueWithToken: "Hard-coded {{property}} value '{{value}}' detected. Use theme token: {{tokenPath}}",
hardCodedValueGeneric:
"Hard-coded {{property}} value '{{value}}' detected. Consider using a theme token from {{categoryHint}}",
hardCodedValueNoContract:
"Hard-coded {{property}} value '{{value}}' detected. Consider using theme tokens for {{category}} values",
replaceWithToken: 'Replace with {{tokenPath}}',
},
},
create(context: Rule.RuleContext): Rule.RuleListener {
const options: ThemeTokenOptions = context.options[0] || {};
return createThemeTokenVisitors(context, options);
},
};
export default rule;