
当命名不再是随意涂鸦AI 辅助 Design Token 命名的语义化与一致性自动检查一、深度引言与场景痛点Design Token 的命名像给花园里的每棵植物贴标签——标签准确所有人都能快速找到需要的植物标签混乱整个花园变成迷宫。现实中Token 命名混乱的根源不是设计师不认真而是命名本身就是一件需要语义推理的脑力活--color-blue-500是原始色值命名--color-primary-action是语义命名同一个 Token 到底用哪种命名语义化命名的核心要求是名字传达用途而非外观——--color-primary-action告诉你这是主要操作按钮的颜色而--color-blue-500只告诉你这是某个蓝色。当设计系统从 20 个 Token 增长到 200 个语义命名的一致性变得至关重要一个 Token 叫--spacing-card-gap另一个叫--gap-card第三个叫--card-spacing-gap——三种命名传达同一个意思但开发者要找三次才能确定。AI 辅助命名检查解决的核心问题自动扫描 Token 命名的一致性和语义合规性发现命名风格不统一、语义层级缺失、命名重复的三类问题并给出标准化建议。二、底层机制与原理深度剖析Token 命名检查的三个维度格式一致性、语义层级完整性、语义重复检测。每个维度用不同的检查策略逐层收拢命名混乱。flowchart TD A[Token 命名清单] -- B[格式一致性检查] B -- B1[命名风格检测kebab-case vs camelCase vs snake_case] B -- B2[层级分隔符检测/ vs - vs _ vs .] B -- B3[前缀规范检测是否统一使用 -- 前缀] B1 B2 B3 -- C[语义层级完整性检查] C -- C1[三层命名覆盖primitive / semantic / component] C -- C2[层级跳过检测缺少中间层级的引用] C -- C3[语义粒度检测过于笼统 vs 过于具体] C1 C2 C3 -- D[语义重复检测] D -- D1[同义命名聚类名字不同但语义相同] D -- D2[别名链检测同一 Token 有多个入口] D -- D3[AI 语义相似度判定近义词命名归并] D1 D2 D3 -- E[标准化建议] E -- E1[命名风格统一方案] E -- E2[缺失层级补全方案] E -- E3[重复命名归并方案]格式一致性检查是最基础的层面。Token 命名必须统一使用一种风格——CSS Variable 用kebab-case--color-primary-actionFigma Variable 用/分隔层级Colors/Primary/ActionJSON Token 用.分隔color.primary.action。如果项目中三种格式混杂转换管道就无法可靠运行。检查器扫描所有 Token 命名统计格式分布当格式不一致率超过 10% 时报警。语义层级完整性检查是更深层的结构问题。命名应该覆盖三层语义原始层--color-blue-500、语义层--color-primary-action、组件层--button-primary-background。如果某个组件直接引用原始层 Tokenbackground: var(--color-blue-500)跳过了语义层设计意图就无法传递——当品牌色从蓝色改为红色时需要逐一修改所有硬编码引用。语义重复检测是最需要 AI 能力的层面。--spacing-card-gap和--gap-card是同义命名但代码层面的字符串匹配无法发现这种重复——它们不是同一个字符串却是同一个意思。AI 用语义理解判定两个命名是否表达相同含义把同义命名聚类后建议归并为一个标准命名。三、生产级代码实现与最佳实践格式一致性检查器// scripts/token-naming-check/format-consistency.ts interface TokenName { raw: string; // 原始命名 segments: string[]; // 分段后的命名层级 separator: string; // 分隔符类型 casing: string; // 命名风格 } interface FormatViolation { token: string; expectedFormat: string; actualFormat: string; suggestion: string; } function checkFormatConsistency(tokens: string[]): FormatViolation[] { const violations: FormatViolation[] []; // 统计项目中主要的命名风格 const styleDistribution new Mapstring, number(); tokens.forEach(t { const style detectNamingStyle(t); styleDistribution.set(style, (styleDistribution.get(style) || 0) 1); }); // 确定主流风格出现最多的 const dominantStyle [...styleDistribution.entries()] .sort((a, b) b[1] - a[1])[0][0]; // 检查每个 Token 是否符合主流风格 tokens.forEach(t { const style detectNamingStyle(t); if (style ! dominantStyle) { violations.push({ token: t, expectedFormat: dominantStyle, actualFormat: style, suggestion: convertToStyle(t, dominantStyle) }); } }); return violations; } // 检测命名风格 function detectNamingStyle(name: string): string { // CSS Variable 以 -- 开头 if (name.startsWith(--)) { // 检查是否使用 kebab-case标准 CSS Variable 风格 if (/^[a-z][a-z0-9]*(-[a-z0-9])*$/.test(name.slice(2))) return kebab-case; // 检查是否使用 camelCase不规范但常见 if (/[a-z][A-Z]/.test(name.slice(2))) return camelCase; return mixed; } // JSON / JS 风格 if (name.includes(.)) return dot-notation; if (name.includes(/)) return slash-notation; if (name.includes(_)) return snake_case; return unknown; } // 将命名转换为目标风格 function convertToStyle(name: string, targetStyle: string): string { // 提取语义片段 const segments name.replace(/^--/, ).split(/[-._/]/); const words segments.map(s s.replace(/([a-z])([A-Z])/g, $1 $2).toLowerCase().split( ) ).flat(); switch (targetStyle) { case kebab-case: return --${words.join(-)}; case camelCase: return --${words[0]}${words.slice(1).map(w w.charAt(0).toUpperCase() w.slice(1) ).join()}; case snake_case: return --${words.join(_)}; default: return name; } }语义层级完整性检查器// scripts/token-naming-check/semantic-hierarchy.ts interface HierarchyViolation { token: string; level: primitive | semantic | component; missingLevel: string; // 缺失的中间层级 suggestion: string; // 补全建议 } function checkSemanticHierarchy( tokens: string[], componentReferences: Mapstring, string[] // 组件 → 引用的 Token 列表 ): HierarchyViolation[] { const violations: HierarchyViolation[] []; // 分类每个 Token 的语义层级 const tokenLevels new Mapstring, string(); tokens.forEach(t { const segments t.replace(/^--/, ).split(-); // 层级判定规则 // 包含具体色值名称blue, red, 500→ primitive // 包含语义用途primary, action, background→ semantic // 包含组件名button, card, input→ component if (segments.some(s /^(blue|red|green|gray|500|400|300|200)$/.test(s))) { tokenLevels.set(t, primitive); } else if (segments.some(s /^(button|card|input|modal|nav|header)$/.test(s))) { tokenLevels.set(t, component); } else { tokenLevels.set(t, semantic); } }); // 检查组件是否直接引用 primitive Token跳过语义层 for (const [component, refs] of componentReferences) { for (const ref of refs) { const level tokenLevels.get(ref); if (level primitive) { violations.push({ token: ref, level: primitive, missingLevel: semantic, suggestion: 组件 ${component} 直接引用原始 Token ${ref}应创建语义 Token 如 --color-${component}-background 引用 ${ref} }); } } } // 检查语义层级是否缺少 component 层级的细化 const semanticTokens tokens.filter(t tokenLevels.get(t) semantic); for (const semToken of semanticTokens) { // 语义 Token 是否被至少一个组件引用 const isReferenced [...componentReferences.values()] .some(refs refs.includes(semToken)); if (!isReferenced) { violations.push({ token: semToken, level: semantic, missingLevel: component, suggestion: 语义 Token ${semToken} 没有被任何组件引用考虑创建组件级 Token 细化用途 }); } } return violations; }AI 语义重复检测// scripts/token-naming-check/semantic-duplicate.ts import { OpenAI } from openai; interface DuplicateGroup { standardName: string; // 建议的标准化命名 duplicates: string[]; // 同义命名列表 reason: string; // 为什么判定为同义 } async function checkSemanticDuplicates( tokens: string[] ): PromiseDuplicateGroup[] { const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const response await openai.chat.completions.create({ model: gpt-4o, messages: [ { role: system, content: 你是 Design Token 语义分析专家。分析以下 Token 命名列表找出表达相同语义但命名不同的 Token 组。 同义判定规则 1. 表达相同设计用途如卡片间距和卡片内部间距 2. 使用不同词汇但指向同一概念如 gap 和 spacing 3. 命名层级不同但最终含义相同如 color-primary 和 color-brand-primary 为每个同义组建议一个标准化命名遵循 kebab-case 和三层语义规则。 }, { role: user, content: Token 命名列表\n${tokens.join(\n)} } ], response_format: { type: json_object }, max_tokens: 4000 }); const result JSON.parse(response.choices[0].message.content || {}); return result.duplicateGroups || []; }综合检查流程编排// scripts/token-naming-check/run-check.ts async function runTokenNamingCheck() { // 从 tokens.css 中提取所有 Token 命名 const tokens extractTokenNames(src/styles/tokens.css); // 从组件代码中提取 Token 引用关系 const componentRefs extractComponentReferences(src/components); // 三层检查并行执行 const formatViolations checkFormatConsistency(tokens); const hierarchyViolations checkSemanticHierarchy(tokens, componentRefs); const duplicateGroups await checkSemanticDuplicates(tokens); // 输出综合报告 const report { summary: { totalTokens: tokens.length, formatIssues: formatViolations.length, hierarchyIssues: hierarchyViolations.length, duplicateGroups: duplicateGroups.length }, format: formatViolations, hierarchy: hierarchyViolations, duplicates: duplicateGroups, timestamp: new Date().toISOString() }; fs.writeFileSync(token-naming-report.json, JSON.stringify(report, null, 2)); // 有严重问题时阻断 CI if (hierarchyViolations.some(v v.level primitive)) { console.error(❌ Token 命名检查失败存在原始层直接引用); process.exit(1); } }四、边界分析与架构权衡语义层级的三层划分不够精确。实际项目中Token 的语义层级可能是四层甚至五层——原始层--blue-500、语义层--primary-action、场景层--primary-action-dark、组件层--button-primary-bg、状态层--button-primary-bg-hover。三层规则过于简化无法覆盖所有场景。解决方案允许项目自定义层级规则检查器根据项目定义的层级配置验证完整性而非强制三层。AI 语义重复判定的边界。--spacing-card-gap和--gap-card确实是同义命名但--color-primary和--color-brand-primary可能不是——前者是全局主色后者是品牌主色品牌更换时两者可能指向不同颜色。AI 的语义理解可能过度归并——把本应区分的 Token 合为一组。解决方案AI 输出的同义组只作为疑似重复标记不自动归并。开发者逐一确认后决定是否合并避免过度归并。命名检查的时机选择。检查可以在 Token 创建时实时拦截、在 CI 中定期扫描、在 PR 合并前变更审查三个时机执行。实时拦截最理想但实现成本高——需要 IDE 插件或 Figma 插件集成。CI 定期扫描最实用但反馈周期长——开发者提交后几小时才知道命名不规范。PR 合并前审查是平衡点——在代码入库前拦截问题反馈及时且不需要 IDE 集成。Token 命名的文化差异。不同团队有不同的命名文化——中文团队可能倾向中英混合命名--color-zhuti英文团队可能倾向纯英文缩写--clr-pri。AI 检查器的标准规则无法覆盖所有文化偏好。解决方案团队定义自己的命名规范文件包含命名风格、层级规则、词汇映射表检查器基于团队规范而非通用规则验证。命名标准化不是全球统一而是团队内部统一。五、总结Token 命名的语义化和一致性不是装饰性问题而是设计系统可维护性的基础设施。命名传达用途而非外观让设计意图在代码中无损传递格式一致让管道可靠运行层级完整让变更的影响范围可控。AI 辅助命名的核心价值在语义重复检测——代码层面的字符串匹配无法发现名字不同但意思相同的命名混乱AI 的语义理解能力恰好填补了这个缺口。但 AI 的判定不是终审疑似重复需要开发者确认后再归并避免语义过度归并。命名检查的终极目标是让 Token 命名成为设计系统的导航图——任何新成员看到--button-primary-background不需要查文档就能理解这是按钮主色的背景值。命名就是文档这就是语义化命名的真正价值。