☰
Sentry eslintPluginScraps 新规则开发指南:四种 Rule Archetype 模式与源码实现解析
2026/10/9 0:26:58 网站建设 项目流程

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 schemarestrict-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):与导入重写在访问期间即刻报告不同,此类规则必须先收集、后校验:

  1. 调用createStyleCollector(context)得到{collector, visitors},将visitors展开进规则的返回值;
  2. 在Program:exit中遍历collector.getAll(),逐个校验每条StyleDeclaration;
  3. 校验结束后调用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)的运行流程可印证:

  1. 先shouldAnalyze(context)快速退出;
  2. createStyleCollector收集后,在validateDeclaration中对每条声明取decl.property.name(已归一化),跳过--开头的 CSS 自定义属性;
  3. 遍历decl.values,凡带tokenInfo的值,用findRuleForToken(tokenPath)查配置(src/config/tokenRules.ts);
  4. 若 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:

  1. 调用createImportTracker()创建追踪器,把它的visitors合并进返回对象,随后在需要处调用resolve(localName)或findLocalNames(source, name)判断某个 JSX 标识符来自哪个导入;
  2. 在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)给出从脚手架到注册的完整链路,这里概括为三步:

  1. 创建文件:规则本体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 导出名。

  2. 写测试:用@typescript-eslint/rule-tester的RuleTester,valid/invalid用例带filename;可修复规则的所有 invalid 用例必须带output字段,描述 autofix 后的期望代码。

  3. 注册启用:在 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):

  1. 是否只改 import 来源字符串?→Archetype 1,autofix 几乎零风险。
  2. 是否校验动态 token/值用在了哪些 CSS 属性?→Archetype 2,两阶段收集 +Program:exit校验,类别数据下沉到 src/config/tokenRules.ts。
  3. 是否限制某个 props/插槽里能放哪些 JSX 组件?→Archetype 3,createImportTracker定位来源 + 递归遍历,autofix 一般不做。
  4. 是否要在 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询