首页
/ Sentry eslintPluginScraps 新规则开发指南:四种 Rule Archetype 模式与源码实现解析

Sentry eslintPluginScraps 新规则开发指南:四种 Rule Archetype 模式与源码实现解析

2026-09-08 21:04:25作者:凌朦慧Richard

本文面向需要为 Sentry 前端设计系统新增 ESLint 规则(尤其是 CSS-in-JS / Emotion 样式与 JSX 结构约束类规则)的开发者,系统讲解 .agents/skills/lint-new/references/rule-archetypes.md 中定义的四类规则原型的选型依据、AST 访问器组织方式、自动修复安全性边界,并对照仓库中 static/oxlint/eslintPluginScraps 的实际实现给出可复用的代码骨架。读完本文,你将能够根据"规则意图"快速确定应采用哪种 AST 方案,并正确复用 createStyleCollectorcreateImportTracker 等共享工具,写出测试完备、可注册、可自动修复的新规则。

一、先读懂这份参考文档的定位

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

  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 会把三路提取器的访问器聚合在一起:

三者共享同一个 collector,并通过 mergeVisitors(同类型节点处理器会被合并串联执行)与 createThemeTrackertracker/theme.ts,负责追踪 useTheme() 及回调中的 theme 绑定)组合,最终返回 {collector, visitors, themeTracker}

重要提醒:collector 只处理模板字符串里的插值表达式${...} 部分,即动态传入 CSS 属性的值),不会分析 quasis 中的静态 CSS 文本。若要在静态文本本身(如裸十六进制颜色、嵌套选择器)中检测模式,请改用 Archetype 4。

2.2 配置驱动:把类别映射放进行 config 目录

如果校验规则按类别变化,应把映射关系放在 src/config/ 中而非写死在规则逻辑里。仓库中 src/config/tokenRules.ts 即此模式的样板:新增类别时通常只需编辑配置文件、无需改动规则逻辑。use-semantic-tokensrc/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 导入,再用正则探测 useThemestyled./(、css 模板字符串及 css=/style= 等 JSX 属性用法;只要命中其一即返回 true。注释明确说明"允许误报(false positives are acceptable)",目的是跳过明显与 Emotion 无关的文件,为全仓库静态检查省下可观的解析开销。

四、Archetype 3:JSX Structural Constraint(JSX 结构约束)

适用场景:规则要限制某个 props/插槽(slot)中允许出现哪些 JSX 元素——例如某些设计系统组件的 slot 只允许放入指定的子组件集合。

模式:组合使用 import 解析器 createImportTrackerJSXAttribute visitor:

  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、三元表达式、逻辑表达式(&&||??)、JSXExpressionContainerJSXFragment、箭头函数体,都需要继续递归;
  • 透明包裹器:跳过 React.Fragment / <Fragment>
  • 命中即停:遇到不允许的元素立即报告并停止递归(避免重复报错)。

配置 schema:由于允许/禁止关系通常是"props × 允许元素集合"的多层嵌套,schema 会比较复杂,文档建议以 restrict-jsx-slot-childrensrc/rules/restrictJsxSlotChildren.ts)为完整范式参照,其配套测试见 restrictJsxSlotChildren.spec.ts

自动修复:一般不安全——替换 JSX 元素需要理解组件 API 契约,这超出了 AST 本身能提供的信息,因此该类规则通常只报告、不做 fix。

仓库对应实现createImportTracker 的契约定义与单测位于 src/ast/tracker/imports.tssrc/ast/tracker/imports.spec.tspreferInfoTextpreferStackForColumnFlex 等规则同样复用了该 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/ 目录下仅存在 extractortrackerutils 三个子模块,尚未见到文档所述的 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,都可能需要先把节点归类。getStyledCallInfosrc/ast/utils/styled.ts)接收一个 TaggedTemplateExpressionCallExpression,返回可辨识联合类型:

  • {kind: 'element', name, tag}styled.div / styled('div')
  • {kind: 'component', name, tag}styled(Component) / styled(Mod.Button)
  • {kind: 'css', tag}:裸 cssX.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.withoutDocsmeta 中声明 type: 'problem'schemamessages;可修复规则需在 meta.fixable: 'code' 中声明。命名遵循 kebab-case 规则名(verb-noun,如 no-token-import)与 camelCase 导出名。

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

  3. 注册启用:在 src/rules/index.tsrules 映射中登记导出;再于 eslint 配置(eslint.config.tsplugin/@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 有更细的修复范式总结):replaceTextreplaceTextRangeinsertTextBefore/Afterremove,可返回单个 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 3createImportTracker 定位来源 + 递归遍历,autofix 一般不做。
  4. 是否要在 CSS 静态文本里抓模式?→ Archetype 4,扫 quasi 静态文本,规则意图与 Archetype 2 恰好互补。

在动手写 AST 遍历前,请先到 eslintPluginScraps/src/ast/ 检查可复用工具(shouldAnalyzegetStyledCallInfocreateImportTrackercreateStyleCollector 等),若发现多个规则共享逻辑,应将其抽入 src/ast/utils/。对样式体系类规则,还可以进一步阅读技能包中的 style-collector-guide.md 以理解 token 收集器的内部约定;对 schema 复杂的需求则参考 schema-patterns.md。这样产出的规则既贴合设计系统语义,又能与 Sentry 既有 lint 基础设施无缝衔接。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391