Sentry 前端语义化 Token 分类体系:use-semantic-token规则的 Token Taxonomy 修复指南
【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry
导读
本文围绕 Sentry 开源仓库中.agents/skills/lint-fix技能体系的核心参考文档token-taxonomy.md展开,系统讲解 Sentry 前端(static/app)中语义化设计令牌(theme.tokens.*)的六大分类(content / background / border / focus / graphics / syntax)及其允许绑定的 CSS 属性。读完本文,你将掌握use-semantic-tokenESLint 违规的判定原理(关键词匹配、最深优先规则)、属性到分类的快速查表方法,以及"保持后缀、仅换分类"的标准修复模式,并理解该规则在 tokenRules.ts 中的真实实现与测试验证方式。
一、背景:Sentry 前端的语义化 Token 体系
Sentry 前端维护了一套基于 Emotion 主题(@emotion/react)的设计令牌体系。主题对象通过theme.tokens.*暴露,实际定义位于 static/app/utils/theme/scraps/theme/light.tsx 与 dark.tsx,两套主题在 1883 行附近均以如下结构导出令牌:
export const lightTheme = { ...baseTheme, shadow, tokens: { background, border, content, dataviz, elevation, focus, graphics, interactive, syntax, }, };从源码结构可以看到,令牌命名空间包含content、background、border、focus、graphics、dataviz、elevation、syntax、interactive等多个顶层分组,而颜色令牌的具体色值则沉淀在 static/app/utils/theme/scraps/tokens/color.tsx 中。令牌路径通常形如theme.tokens.interactive.chonky.neutral.content这样的多段式命名,每一段都携带语义信息——这正是use-semantic-token规则能够做静态分类判定的前提。
为了防止开发者在样式代码中把"语义错误"的令牌(例如用border分类的令牌去写color)滥用,Sentry 在@sentry/scrapsESLint 插件中实现了use-semantic-token规则。该规则没有自动修复(autofix),因此需要开发者依据token-taxonomy.md手动修复,这也是本文所要展开的全部内容。
二、Token 六大分类与允许的 CSS 属性(核心表)
token-taxonomy.md的第一张核心表定义了六个令牌分类各自的关键词(出现在令牌路径中用于识别)与允许的 CSS 属性。这张表正是 tokenRules.ts 中TOKEN_RULES数组的文档化映射,源码中每个规则由name、keywords、allowedProperties三个字段构成。
| 分类 | 路径关键词 | 允许的 CSS 属性 |
|---|---|---|
| content | content、link | color、text-decoration、text-decoration-color、text-emphasis-color、caret-color、column-rule-color、-webkit-text-fill-color、-webkit-text-stroke-color、fill、stop-color |
| background | background | background、background-color、background-image |
| border | border | border、border-color、border-top、border-right、border-bottom、border-left、所有border-*-color变体、border-block*、border-inline*、stroke、text-decoration、text-decoration-color、border-image、border-image-source |
| focus | focus、elevation | box-shadow、outline、outline-color、text-shadow |
| graphics | graphics、dataviz | background、background-color、background-image、fill、stroke、stop-color |
| syntax | syntax | color、-webkit-text-fill-color、-webkit-text-stroke-color、background、background-color、background-image |
对照源码 tokenRules.ts 可以确认以下几点实现细节:
- content 分类允许
fill与stop-color,这使其可以用于 SVG 文本着色; - border 分类不仅覆盖四个方向的颜色变体(
border-top-color、border-right-color、border-bottom-color、border-left-color),还覆盖逻辑属性border-block*与border-inline*系列(含-color、-start、-end等共 12 个变体),同时放宽了stroke、text-decoration、border-image、border-image-source; - focus 分类专门服务于交互态反馈,允许
box-shadow、outline、outline-color、text-shadow,关键词同时包含focus与elevation; - graphics 分类关键词为
graphics与dataviz,用于图表/数据可视化场景; - syntax 分类专门用于代码高亮场景,是唯一同时允许文本色(
color、-webkit-text-*)和背景色(background*)的分类。
依据 light.tsx 的实际导出,
tokens下还包含dataviz、elevation、interactive分组。其中dataviz与graphics在规则中共享同一分类,elevation与focus共享同一分类,而interactive本身不作为独立分类,它内部按交互状态细分出的子令牌(如interactive.link.neutral.rest)会继续用更深的content等关键词决定归属。
三、CSS 属性 → 正确分类的快速查表
token-taxonomy.md的第二张表是反向查表:给定一个 CSS 属性,应该从哪个分类取令牌。这张表与源码中PROPERTY_TO_RULE反向映射(tokenRules.ts)一一对应——后者由buildPropertyToRule遍历所有规则的allowedProperties生成property → rule name的 Map,正是use-semantic-token规则报告 "Use a{{suggestedCategory}}token instead" 建议信息的来源。
| CSS 属性 | 应使用的令牌分类 |
|---|---|
color | content(代码高亮场景用syntax) |
background、background-color | background(数据可视化场景用graphics) |
border、border-color、border-* | border |
box-shadow | focus |
outline、outline-color | focus |
text-shadow | focus |
fill、stroke | content(文字)、graphics(图表)、或border(装饰性) |
text-decoration、text-decoration-color | content或border |
使用口诀:先按语义猜属性,再用表格核对分类。例如要给图表柱形上色用stroke,查表应取graphics分类令牌;要给普通文本设color,取content;要写键盘焦点样式outline-color,只能取focus。
四、关键词匹配策略:为何"最深优先"?
token-taxonomy.md规定令牌路径按关键词匹配,规则为:
- 将路径按
.分割成段; - 找出哪个分类的关键词出现在路径中**最深(最后)**的位置;
- 该分类的规则生效。
例如interactive.background.content→content胜出(因为它比background更深)。
这一策略在源码中由findRuleForToken(tokenRules.ts)精确实现:
export function findRuleForToken(tokenPath: string): TokenRule | null { const pathParts = tokenPath.split('.'); let bestMatch = null; let bestPosition = -1; for (const rule of TOKEN_RULES) { for (const keyword of rule.keywords) { const position = pathParts.lastIndexOf(keyword); if (position > bestPosition) { bestMatch = rule; bestPosition = position; } } } return bestMatch; }实现要点:对每个规则关键词用lastIndexOf取其在路径中最后一次出现的位置,取位置最大(即路径最深层)的关键词所属规则。之所以采用"最深优先"而非"最先出现优先",是因为 Sentry 的令牌路径设计遵循"外层是组件状态,内层是语义"的约定——例如interactive.chonky.debossed.neutral.content.primary中,interactive与chonky只是交互形态,真正决定"这个令牌能用来做什么"的是最深的content。测试用例 useSemanticToken.spec.ts 中的interactive.chonky.debossed.neutral.content.primary、interactive.link.neutral.rest等路径被标记为color属性的合法用例,正是对这一策略的验证。
当路径中不含任何已知关键词时,findRuleForToken返回null,规则直接跳过该值(见 useSemanticToken.ts)。
五、修复模式:保持后缀,仅换分类
token-taxonomy.md给出的修复模式非常简洁:保持同样的特异性后缀,只更换分类前缀。因为各分类的令牌在相同语义层级(primary、secondary、accent、warning、danger等)上是对应的,直接替换即可保持视觉意图。
// Before(错误:用 border 令牌给 color 赋值) color: ${p => p.theme.tokens.border.primary}; // After(正确:color 应使用 content 令牌) color: ${p => p.theme.tokens.content.primary};配套的 fix-patterns.md 提供了更丰富的对照示例:
| CSS 属性 | 错误令牌 | 正确令牌 |
|---|---|---|
color | theme.tokens.border.primary | theme.tokens.content.primary |
background | theme.tokens.content.primary | theme.tokens.background.primary |
border-color | theme.tokens.background.secondary | theme.tokens.border.secondary |
box-shadow | theme.tokens.content.primary | theme.tokens.focus.primary或theme.tokens.elevation.* |
fix-patterns.md同时强调:use-semantic-token没有 autofix(这是与no-core-import的关键区别),必须人工确认每个修复点。如果不确定某个具体令牌名是否存在,可以查看主题定义文件,或在 IDE 中利用theme.tokens.<category>.的自动补全来探索可用的令牌名。
六、规则实现原理:use-semantic-token如何工作
了解规则如何判定违规,有助于写出更精准的修复。规则的完整实现位于 useSemanticToken.ts,其核心流程如下:
- 快速退出:文件不包含 emotion/styled 模式时直接跳过(
shouldAnalyze); - 收集样式声明:通过
createStyleCollector收集 styled 模板字符串、css模板、style prop 等场景的StyleDeclaration; - 逐个校验:对每个声明的属性,若属性名以
--开头(CSS 自定义属性)则跳过;对声明值列表中的每个值,若带有tokenInfo则调用findRuleForToken解析分类; - 比对白名单:若分类未启用或属性在
allowedProperties中则通过;否则报告错误——若反向映射PROPERTY_TO_RULE能给出建议分类,则报invalidPropertyWithSuggestion(带建议),否则报invalidProperty。
规则还支持enabledCategories选项,用于只启用部分分类的检查(useSemanticToken.ts)。
令牌提取与值分解:tokenInfo由 valueDecomposer.ts 生成。它会递归分解复杂表达式:
- 三元表达式
foo ? a : b:两个分支都作为可能值检查; - 逻辑表达式
a || b、a && b:两个操作数都可能被检查; - 箭头函数
p => p.theme.tokens.content.primary:注册参数p为 theme 绑定后递归分析函数体; - 成员表达式链:向上走完整个
MemberExpression链,找到tokens段,取其后缀为tokenPath。
theme 绑定追踪:为了避免误报,theme.ts 会做作用域感知的绑定分析——追踪import {useTheme} from '@emotion/react'、const theme = useTheme()、const {tokens} = useTheme(),以及回调参数(theme) => ...、(p) => p.theme等绑定形式;只有确认基标识符是已知 theme 绑定或theme/p/t等约定名时才识别为令牌引用(见 valueDecomposer.ts)。
测试验证:测试文件 useSemanticToken.spec.ts 覆盖了大量场景,包括合法用例(color配content、background配background、border-color配border、嵌套伪类/媒体查询/模板插值对象等)与违规用例(如background: ${p => p.theme.tokens.content.primary}建议syntax、border-color配content.accent建议border、box-shadow配content.primary建议focus等),其中还验证了"单个表达式含多个令牌(三元)时逐个上报"的行为。
七、实战工作流:从违规统计到批量修复
token-taxonomy.md定位是lint-fix技能在人工修复use-semantic-token违规时必须加载的参考文档(见 .agents/skills/lint-fix/SKILL.md)。结合 SKILL.md 给出的工作流,一次完整的修复流程如下:
1. 统计违规规模
pnpm exec eslint --rule '@sentry/scraps/use-semantic-token: error' "$1" 2>&1 | tail -5最后一行即违规总数(如42 problems (42 errors, 0 warnings))。注意:对整个static/app/跑 ESLint 可能耗时 2 分钟以上,建议先缩小到子目录。
2. 按规模选择策略
- 100 个以内:手动逐个修复,每批 5~10 个文件,每批后重跑验证;
- 100~500 个:按子目录分批(如
static/app/views/、static/app/components/),每批提交一个可审查的 PR; - 500 个以上:考虑用 jscodeshift 等 codemod 做机械替换,或先将规则以
warn级别灰度开启,分多个 PR 推进。
3. 严格遵循修复闭环
- 运行
pnpm exec eslint --rule '@sentry/scraps/use-semantic-token: error' "$1"定位违规; - 依据本文第二、三节表格确定正确分类,保持后缀替换令牌;
- 对已改文件重跑 ESLint 确认清零;
- 扩大范围,循环往复;全部完成后对
static/app/整体验证应报告0 problems。
提交前还可用.venv/bin/prek run -q --files <file1> [file2 ...]对改动文件跑 pre-commit 检查,并按@.github/CODEOWNERS的归属约定控制单个 PR 的改动量(约 50 个文件),PR 标题遵循fix(lint): enforce @sentry/scraps/use-semantic-token for <codeowner>的约定。
八、易错场景与排查建议
- 伪类与嵌套选择器中的令牌:
a:hover、&:focus、&::before、@media查询内的令牌都会被正常收集与校验(测试中有对应合法用例),修复时不要忽略嵌套作用域; - 多令牌表达式:一个三元表达式两个分支都违规时,规则会分别报告两处(见 useSemanticToken.spec.ts),修复时需同时替换所有分支;
- 字面量与混合值:纯字面量(如
background: red)不触发规则,混合了令牌与字面量的对象取值(({none: tokens.content.secondary, alert: colors.yellow500})[status])只校验令牌部分; - CSS 自定义属性:
--*开头的属性一律跳过,这是为了避免把动态变量绑定误判为令牌滥用; - 不确定分类归属时:优先看主题定义 light.tsx / dark.tsx 中对应分组的结构,或依赖 IDE 对
theme.tokens.<category>.的自动补全。
总结
Sentry 的语义化 Token 分类体系通过content、background、border、focus、graphics、syntax六大分类约束了每个令牌的合法使用范围,use-semantic-token规则与token-taxonomy.md共同构成了这套约束的"裁判 + 手册"。修复违规的要点可以概括为一句话:按 CSS 属性查分类表,保持特异性后缀不变,仅替换分类前缀。理解tokenRules.ts中"关键词最深优先"的匹配算法、PROPERTY_TO_RULE的反向建议机制,以及useSemanticToken.ts中基于 AST 提取与 theme 绑定追踪的判定流程,能让你在规模化修复时更快、更稳地定位并消灭全部违规。
【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考