Sentry eslintPluginScraps 新规则开发指南:四种 Rule Archetype 模式与源码实现解析
【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry
本文面向需要为 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(导入重写) | ImportDeclarationvisitor,配合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 的来源并重写它——例如禁止从某个内部模块导入、统一改写为新的包路径。
核心模式:只写一个ImportDeclarationvisitor,自动修复(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与JSXAttributevisitor:
- 调用
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 基础设施无缝衔接。
【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考