import { ESLintUtils } from '@typescript-eslint/utils' const RE_HEX3 = /^#[0-9a-fA-F]{3}$/ const RE_HEX6 = /^#[0-9a-fA-F]{6}$/ const RE_HEX8 = /^#[0-9a-fA-F]{8}$/ const RE_RGB = /^rgb\s*\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/ const RE_RGBA = /^rgba\s*\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)$/ const RE_HSL = /^hsl\s*\(\s*\d+\s*,\s*[\d.]+%?\s*,\s*[\d.]+%?\s*\)$/ const ALLOWED_KEYWORDS = new Set([ 'inherit', 'transparent', 'currentColor', 'none', 'unset', 'initial', 'auto', 'contain', 'cover', ]) function isHardcodedColor(value) { if (typeof value !== 'string') return false const trimmed = value.trim() if (ALLOWED_KEYWORDS.has(trimmed.toLowerCase())) return false if (trimmed.startsWith('var(')) return false if (/^\d+(\.\d+)?px?$/.test(trimmed)) return false if (/^\d+(\.\d+)?\%$/.test(trimmed)) return false return ( RE_HEX3.test(trimmed) || RE_HEX6.test(trimmed) || RE_HEX8.test(trimmed) || RE_RGB.test(trimmed) || RE_RGBA.test(trimmed) || RE_HSL.test(trimmed) ) } function extractStyleProperties(expression) { const properties = [] if (expression.type === 'ObjectExpression' && expression.properties) { for (const styleProp of expression.properties) { if ( styleProp.type === 'Property' && styleProp.key?.type === 'Identifier' && styleProp.value?.type === 'Literal' && typeof styleProp.value.value === 'string' ) { properties.push({ key: styleProp.key.name, value: styleProp.value.value, loc: styleProp.value.loc, }) } } } return properties } export const RULE_NAME = 'no-hardcoded-color-in-style' export default ESLintUtils.RuleCreator((name) => { return `https://eslint.dev/rules/#${name}` })({ name: 'no-hardcoded-color-in-style', meta: { type: 'problem', docs: { description: 'Disallow hardcoded color values in JSX style properties', recommended: false, }, messages: { hardcodedColor: '硬编码的颜色值 "{{value}}" 不允许使用。请使用 TDesign CSS Token(如 var(--td-text-color-placeholder))代替。', }, schema: [], }, create(context) { return { JSXAttribute(node) { if ( node.name?.type === 'JSXIdentifier' && node.name.name === 'style' && node.value?.type === 'JSXExpressionContainer' && node.value.expression ) { const styleProps = extractStyleProperties(node.value.expression) for (const prop of styleProps) { if (isHardcodedColor(prop.value)) { context.report({ node: context.sourceCode.getLastToken(node), messageId: 'hardcodedColor', data: { value: prop.value }, }) } } } }, } }, })