Sentry eslintPluginScraps 新规则开发指南:四种 Rule Archetype 模式与源码实现解析
本文面向需要为 Sentry 前端设计系统新增 ESLint 规则(尤其是 CSS-in-JS / Emotion 样式与 JSX 结构约束类规则)的开发者,系统讲解 .agents/skills/lint-new/references/rule-archetypes.md 中定义的四类规则原型的选型依据、AST 访问器组织方式、自动修复安全性边界,并对照仓库中 static/oxlint/eslintPluginScraps 的实际实现给出可复用的代码骨架。读完本文,你将能够根据"规则意图"快速确定应采用哪种 AST 方案,并正确复用 createStyleCollector、createImportTracker 等共享工具,写出测试完备、可注册、可自动修复的新规则。
一、先读懂这份参考文档的定位
Sentry 前端仓库拥有一套独立的 lint 插件工程 eslintPluginScraps(位于 static/oxlint/eslintPluginScraps),其中集中了针对设计系统、样式 token 与 CSS-in-JS 用法的规则。由于这类规则的 AST 遍历逻辑高度相似,仓库以"技能包"形式沉淀了开发规范:lint-new/SKILL.md 描述新建规则的完整流程,而 rule-archetypes.md 则是选型与模式速查——它把"你想让规则做什么"与"应该采用哪种 AST 方案"一一对应,本文即以该文档为核心骨架展开。
规则意图与原型(Archetype)的对应关系是全文的出发点,可用下面的决策表快速定位:
| 规则意图 | Archetype | 关键模式 | 示例规则 |
|---|---|---|---|
| 重写 import 路径 | Import rewrite(导入重写) | ImportDeclaration visitor,配合 fixer.replaceText(node.source, ...) |
no-core-import |
| 校验某个 token/值用于哪些 CSS 属性 | Property validation(属性校验) | createStyleCollector + Program:exit 延迟校验 |
use-semantic-token |
| 限制特定 props 中允许出现的 JSX 元素 | JSX structural constraint(JSX 结构约束) | import 追踪 + 递归 JSX 树遍历 + options schema | restrict-jsx-slot-children |
| 在静态 CSS 文本中检测模式(选择器、原始值) | Template text analysis(模板文本分析) | TaggedTemplateExpression → 遍历 quasi.quasis 静态文本 |
no-dom-coupling(PR #109906) |
下面逐一展开四个原型,并结合 eslintPluginScraps/src 的真实源码佐证。
二、Archetype 1:Import Rewrite(导入重写)
适用场景:规则需要检查 import 的来源并重写它——例如禁止从某个内部模块导入、统一改写为新的包路径。
核心模式:只写一个 ImportDeclaration visitor,自动修复(autofix)就是替换 source 字符串,无需其它 AST 操作:
create(context) {
return {
ImportDeclaration(node) {
const importPath = node.source.value;
if (typeof importPath === 'string' && importPath.startsWith(FORBIDDEN)) {
context.report({
node,
messageId: '...',
fix(fixer) {
return fixer.replaceText(node.source, `'${newPath}'`);
},
});
}
},
};
}
自动修复安全性:几乎总是安全的——修复仅改变一个字符串字面量,不触碰标识符、不改变作用域。
边界情况:type-only 导入(import type {...})、混合具名导入、re-export(export {...} from)都由 ImportDeclaration 统一覆盖,因为只替换 source 字符串,无需特殊处理。注意判断 node.source.value 为字符串类型(跳过动态导入等非字面量场景)再执行 .startsWith(FORBIDDEN)。
仓库对应实现:仓库中的 noCoreImport 规则(见 src/rules/noCoreImport.ts)即此模式的典范,SKILL.md 也明确将其列为"safe autofix patterns"的 canonical 示例。
三、Archetype 2:Property Validation(Style Collector 属性校验)
适用场景:规则要校验"某个动态值(theme token、变量)被用在了哪些 CSS 属性上",典型如 use-semantic-token——它强制 theme.tokens.* 只能搭配与其语义类别匹配的 CSS 属性。
关键洞察——两阶段设计(two-phase):与导入重写在访问期间即刻报告不同,此类规则必须先收集、后校验:
- 调用
createStyleCollector(context)得到{collector, visitors},将visitors展开进规则的返回值; - 在
Program:exit中遍历collector.getAll(),逐个校验每条StyleDeclaration; - 校验结束后调用
collector.clear()做清理。
create(context) {
if (!shouldAnalyze(context)) return {}; // Fast bailout
const {collector, visitors} = createStyleCollector(context);
return {
...visitors,
'Program:exit'() {
for (const decl of collector.getAll()) {
// decl.property.name — the CSS property (already normalized)
// decl.values — array of {rawNode, tokenInfo: {tokenPath, node}}
validateDeclaration(decl);
}
collector.clear();
},
};
}
2.1 createStyleCollector 的底层实现
在 src/ast/extractor/index.ts 中,createStyleCollector 会把三路提取器的访问器聚合在一起:
createStyledExtractor(styled 模板字面量,见 extractor/styled.ts);createCssPropExtractor(Emotion 的cssprop,见 extractor/cssProp.ts);createStylePropExtractor(原生styleprop,见 extractor/styleProp.ts)。
三者共享同一个 collector,并通过 mergeVisitors(同类型节点处理器会被合并串联执行)与 createThemeTracker(tracker/theme.ts,负责追踪 useTheme() 及回调中的 theme 绑定)组合,最终返回 {collector, visitors, themeTracker}。
重要提醒:collector 只处理模板字符串里的插值表达式(${...} 部分,即动态传入 CSS 属性的值),不会分析 quasis 中的静态 CSS 文本。若要在静态文本本身(如裸十六进制颜色、嵌套选择器)中检测模式,请改用 Archetype 4。
2.2 配置驱动:把类别映射放进行 config 目录
如果校验规则按类别变化,应把映射关系放在 src/config/ 中而非写死在规则逻辑里。仓库中 src/config/tokenRules.ts 即此模式的样板:新增类别时通常只需编辑配置文件、无需改动规则逻辑。use-semantic-token(src/rules/useSemanticToken.ts)的运行流程可印证:
- 先
shouldAnalyze(context)快速退出; createStyleCollector收集后,在validateDeclaration中对每条声明取decl.property.name(已归一化),跳过--开头的 CSS 自定义属性;- 遍历
decl.values,凡带tokenInfo的值,用findRuleForToken(tokenPath)查配置(src/config/tokenRules.ts); - 若 token 所属类别的
allowedProperties不包含当前属性,则报告invalidProperty或带建议的invalidPropertyWithSuggestion(后者借助PROPERTY_TO_RULE反查"该属性应使用哪个类别的 token")。
此外该规则还支持 enabledCategories 选项,用于按需开启/关闭某些 token 类别,对应 SKILL.md 中提到的复杂 schema 可参考 references/schema-patterns.md。
2.3 shouldAnalyze:必写的快速预检
文档要求始终用 shouldAnalyze 做快速预扫描退出。其实现见 src/ast/extractor/index.ts:先检查源码是否包含 @emotion/styled 或 @emotion/react 导入,再用正则探测 useTheme、styled./(、css 模板字符串及 css=/style= 等 JSX 属性用法;只要命中其一即返回 true。注释明确说明"允许误报(false positives are acceptable)",目的是跳过明显与 Emotion 无关的文件,为全仓库静态检查省下可观的解析开销。
四、Archetype 3:JSX Structural Constraint(JSX 结构约束)
适用场景:规则要限制某个 props/插槽(slot)中允许出现哪些 JSX 元素——例如某些设计系统组件的 slot 只允许放入指定的子组件集合。
模式:组合使用 import 解析器 createImportTracker 与 JSXAttribute visitor:
- 调用
createImportTracker()创建追踪器,把它的visitors合并进返回对象,随后在需要处调用resolve(localName)或findLocalNames(source, name)判断某个 JSX 标识符来自哪个导入; - 在
JSXAttribute中,当发现配置命中的 prop 时,递归遍历其 JSX 子树,逐一将元素与允许集合比对。
create(context) {
const importTracker = createImportTracker();
return {
...importTracker.visitors,
JSXAttribute(node) {
// Use importTracker.resolve(displayName) to check where an element comes from
// Use importTracker.findLocalNames(source, name) to find local aliases
},
};
}
4.1 需要处理的几种关键模式
- 导入别名:
import {Foo as Bar}使Bar成为本地名,importTracker.resolve('Bar')应返回{source, imported: 'Foo'}; - 成员表达式:
MenuComponents.Alert必须按${localName}.${member}的形式匹配; - 递归穿透:直接 JSX children、三元表达式、逻辑表达式(
&&、||、??)、JSXExpressionContainer、JSXFragment、箭头函数体,都需要继续递归; - 透明包裹器:跳过
React.Fragment/<Fragment>; - 命中即停:遇到不允许的元素立即报告并停止递归(避免重复报错)。
配置 schema:由于允许/禁止关系通常是"props × 允许元素集合"的多层嵌套,schema 会比较复杂,文档建议以 restrict-jsx-slot-children(src/rules/restrictJsxSlotChildren.ts)为完整范式参照,其配套测试见 restrictJsxSlotChildren.spec.ts。
自动修复:一般不安全——替换 JSX 元素需要理解组件 API 契约,这超出了 AST 本身能提供的信息,因此该类规则通常只报告、不做 fix。
仓库对应实现:createImportTracker 的契约定义与单测位于 src/ast/tracker/imports.ts 与 src/ast/tracker/imports.spec.ts;preferInfoText、preferStackForColumnFlex 等规则同样复用了该 tracker。
五、Archetype 4:Template Text Analysis(模板静态文本分析)
适用场景:规则要在模板字符串的静态 CSS 文本(而非插值表达式)中检测模式——原始颜色值、嵌套选择器、CSS 属性名等。
模式:参考文档给出的推荐做法是使用 createQuasiScanner(按文档所述位于 src/ast/scanner/index.ts),它会替你完成三件事:shouldAnalyze 快速退出、通过 getStyledCallInfo 做 tag 识别、以及 quasi 迭代:
import {createQuasiScanner} from '../ast/scanner/index';
create(context) {
return createQuasiScanner(context, (cssText, quasi, info) => {
// cssText: the static CSS text of this quasi segment
// quasi: the TemplateElement node (use for error reporting)
// info: { kind: 'element' | 'component' | 'css', name?: string }
for (const match of cssText.matchAll(MY_PATTERN)) {
context.report({ node: quasi, messageId: '...' });
}
});
}
scanner 会对文件中每一个 styled/css 标签模板的每个 quasi 段调用你的 analyze 回调,并自动跳过没有 Emotion 用法的文件。
说明:在本仓库当前快照中,
src/ast/目录下仅存在extractor、tracker、utils三个子模块,尚未见到文档所述的scanner目录;quasi 静态文本的处理目前由 extractor/styled.ts(从node.quasi.quasis[index]取 preceding quasi 文本)与各规则自身的遍历承担,例如 noDoubleDollarInterpolation.ts 直接遍历node.quasi.quasis、用quasi.tail判断尾段、并用quasi.range定位报告区间。迁移到统一 scanner 属于可预期的演进方向,写作规则时按文档约定调用createQuasiScanner即可保持前瞻性。
5.1 与 Archetype 2 的取舍
这是最容易混淆的一对,决策规则是:
- 目标在 CSS 文本本身(裸颜色、嵌套选择器、属性名)→ 用
createQuasiScanner(Archetype 4); - 目标是校验通过插值传给 CSS 属性的值(
${theme.tokens.X})→ 用createStyleCollector(Archetype 2)。
5.2 标签识别工具:getStyledCallInfo
无论走 scanner 还是自定义 visitor,都可能需要先把节点归类。getStyledCallInfo(src/ast/utils/styled.ts)接收一个 TaggedTemplateExpression 或 CallExpression,返回可辨识联合类型:
{kind: 'element', name, tag}:styled.div/styled('div');{kind: 'component', name, tag}:styled(Component)/styled(Mod.Button);{kind: 'css', tag}:裸css或X.css;null:无法归类。
分类逻辑要点(见 classifyTag / classifyStyledArgs):含 . 的点号名(Mod.Button)一律视为 component;以小写字母开头视为 HTML element,否则为 component;styled(Component).attrs({...}) 会被解包、递归分类内层调用;同时通过 isIntermediateCall 跳过中间层 CallExpression(如 styled(X) 本身),确保只有最外层表达式被归类、避免同一模式被重复命中。该工具已有完整单测 src/ast/utils/styled.spec.ts。
六、新规则的标准落地流程(skill 流程串讲)
在确定原型之后,SKILL.md(.agents/skills/lint-new/SKILL.md)给出从脚手架到注册的完整链路,这里概括为三步:
-
创建文件:规则本体
static/oxlint/eslintPluginScraps/src/rules/$RULE_NAME.ts+ 同名.spec.ts测试。规则体基于ESLintUtils.RuleCreator.withoutDocs,meta中声明type: 'problem'、schema、messages;可修复规则需在meta.fixable: 'code'中声明。命名遵循 kebab-case 规则名(verb-noun,如no-token-import)与 camelCase 导出名。 -
写测试:用
@typescript-eslint/rule-tester的RuleTester,valid/invalid用例带filename;可修复规则的所有 invalid 用例必须带output字段,描述 autofix 后的期望代码。 -
注册启用:在 src/rules/index.ts 的
rules映射中登记导出;再于 eslint 配置(eslint.config.ts内plugin/@sentry/scraps段)以'@sentry/scraps/$RULE_NAME': 'error'或带 options 的数组形式启用。
随后运行测试验证:
pnpm test-ci "static/oxlint/eslintPluginScraps/src/rules/$RULE_NAME.spec.ts"
自动修复的边界
默认立场是"能修就修",但以下情况不应 autofix:
- 存在多个合法修法、需人工判断取舍;
- 修复需要 AST 之外的类型信息;
- 变换会改变控制流或运行时行为;
- 修改跨越多个文件。
Fixer API 常用能力(lint-fix 技能中的 fix-patterns 有更细的修复范式总结):replaceText、replaceTextRange、insertTextBefore/After、remove,可返回单个 fix 或数组。
扩展既有规则时的注意事项
若修改的是配置驱动规则(如 use-semantic-token),改动往往只在配置文件(如 src/config/tokenRules.ts);同时要警惕反向映射副作用——buildPropertyToRule 是"后写覆盖"(last writer wins),新增类别可能改变共享属性的推荐类别,需同步审视既有测试并补新用例。
七、小结:一张选型心法图
面对新的规则诉求,可以按以下顺序自问(对应完整参考见 rule-archetypes.md):
- 是否只改 import 来源字符串?→ Archetype 1,autofix 几乎零风险。
- 是否校验动态 token/值用在了哪些 CSS 属性?→ Archetype 2,两阶段收集 +
Program:exit校验,类别数据下沉到 src/config/tokenRules.ts。 - 是否限制某个 props/插槽里能放哪些 JSX 组件?→ Archetype 3,
createImportTracker定位来源 + 递归遍历,autofix 一般不做。 - 是否要在 CSS 静态文本里抓模式?→ Archetype 4,扫 quasi 静态文本,规则意图与 Archetype 2 恰好互补。
在动手写 AST 遍历前,请先到 eslintPluginScraps/src/ast/ 检查可复用工具(shouldAnalyze、getStyledCallInfo、createImportTracker、createStyleCollector 等),若发现多个规则共享逻辑,应将其抽入 src/ast/utils/。对样式体系类规则,还可以进一步阅读技能包中的 style-collector-guide.md 以理解 token 收集器的内部约定;对 schema 复杂的需求则参考 schema-patterns.md。这样产出的规则既贴合设计系统语义,又能与 Sentry 既有 lint 基础设施无缝衔接。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00