Sentry eslintPluginScraps 的 Style Collector 指南用 createStyleCollector 编写 CSS-in-JS 语义 lint 规则【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry本文聚焦 Sentry 仓库内置的设计系统 ESLint 插件eslintPluginScraps源码位于 static/oxlint/eslintPluginScraps深度讲解其核心共享工具Style CollectorcreateStyleCollector的架构、数据结构与两阶段使用模式。读者将掌握如何编写一类特殊规则——不是检查静态 CSS 文本而是校验styled.div\...、css{}、style{}中**通过插值传入的动态值**尤其是theme.tokens.*语义化 token与 CSS 属性的搭配关系并清楚其与createQuasiScanner 等静态文本分析工具的边界。1. Style Collector 是什么、何时该用1.1 定位与出处style-collector-guide.md是.agents/skills/lint-new技能SKILL.md为新增一条 lint 规则流程准备的参考资料之一。技能的核心流程是阅读 rule-archetypes.md选择匹配你意图的archetype原型检查 src/ast 下可复用的共享工具避免重复实现 AST 遍历按模板创建src/rules/$RULE_NAME.ts与其.spec.ts测试注册规则并运行pnpm test-ci。其中第四类 archetypeProperty validation属性校验就是 Style Collector 的主战场你想校验某个动态值theme token、变量被用在了哪个 CSS 属性上。仓库中use-semantic-token规则是该 archetype 的规范范例。1.2 适用场景动态值分析根据 style-collector-guide.md当规则需要以下能力时使用createStyleCollector校验某个 theme token 被用于哪些 CSS 属性如theme.tokens.content.primary是否被错误地赋给background检查插值值是否符合期望的类型或类别分析 CSS 属性与其动态值之间的关系。这三条共同点关注的永远是interpolation插值表达式也就是模板字符串${...}、css{{...}}对象属性值、style{{...}}值——而不是静态写死的 CSS 文本。1.3 明确不适用什么时候别用它文档给出三条清晰的反向清单违反它们正是新手最常见的错误你的需求应该用检测静态 CSS 文本中的模式十六进制颜色、嵌套选择器等createQuasiScannersrc/ast/scanner/index.ts见 skill 文档中 Template Text Analysis archetype检查 import 路径ImportDeclarationvisitor见no-core-import规则限制 JSX 元素在特定 prop 中出现JSX 树遍历 createImportTracker见restrict-jsx-slot-children规则从当前插件源码目录结构看ast 下已落地的是extractor/、tracker/、utils/三组模块scanner/尚属技能文档规划中面向未来静态文本扫描 archetype 的 API。写规则前先对照 SKILL.md 第 2 步的共享工具表做判断是最省力的方式。2. 架构createStyleCollector 的内部构成文档给出的架构图与实际源码 extractor/index.ts 完全吻合File: src/ast/extractor/index.ts 仓库真实路径见 static/oxlint/eslintPluginScraps/src/ast/extractor/ createStyleCollector(context) ├── createThemeTracker() ← 追踪 useTheme() / 回调式 theme 绑定 ├── createStyledExtractor() ← 处理 styled.div...、styled(X)...、css...、styled.div({...}) ├── createCssPropExtractor() ← 处理 JSX 上的 css{} / css... prop └── createStylePropExtractor() ← 处理 style{{}} prop 返回: { collector, visitors, themeTracker }关键实现细节值得逐一对照源码工厂顺序有依赖createStyleCollector先创建themeTracker再把{collector, themeTracker, ruleContext}组装成ExtractorContext传给三个 extractor——因为 styled/css 提取器在分解表达式时需要询问 theme tracker当前作用域里哪个变量是 theme见 index.ts 与 types.ts。visitors 会被合并mergeVisitors把 theme tracker 与三个 extractor 返回的监听器按节点类型合并同一节点类型存在多个 handler 时依次链式调用见 index.ts。这就是为什么使用方只需一行...visitors展开就能同时获得所有提取能力。Styled extractor 覆盖面最广。源码注释与遍历逻辑styled.ts显示它统一处理styled.div\.../styled(Component)...TaggedTemplateExpression visitorstyled.div({...})/styled(div)({...})对象语法CallExpressionvisitor以及css\... 标签模板。而 cssProp.ts 面向 JSX 的csspropdiv css{css\...} /、css{{...}}、css{[...]}、css{(theme) ({...})}[styleProp.ts](https://link.gitcode.com/i/9ef7d0e14391501828caed200bd44146) 则覆盖div style{{...}} /。四条采集路径最终都汇入同一个collector。3. Collector 捕获的数据模型3.1 每个 StyleDeclaration 的形态collector.getAll()返回的StyleDeclaration是属性声明property与所有可能取值values的中间表示IR。style-collector-guide.md给出的是经过简化的视图真实定义在 extractor/types.ts信息更完整interface StyleDeclaration { context: StyleContext; // 来源文件、scopeId、当前生效的 themeBinding kind: styled | css-prop | style-prop | theme; // 声明出现在哪种样式上下文 property: { name: string; // 归一化后的 CSS 属性名如 background-color node: TSESTree.Node; // 属性名 AST 节点用于报错定位 }; raw: { containerNode: TSESTree.Node; // 承载容器节点TemplateLiteral / ObjectExpression sourceNode: TSESTree.Node; // 根部的 styled/css/style 节点 }; values: StyleValue[]; }每个values[i]types.ts除了文档提到的rawNode与tokenInfo还包含两个对校验规则很有用的字段interface StyleValue { confident: boolean; // 该值能否被静态分析确定不能确定则为 false kind: literal | template-quasi | member | call | conditional | logical | unknown; // 值表达式的类型 node: TSESTree.Node; // 值节点定位/报告用 tokenInfo: TokenInfo | null; }其中TokenInfo记录主题 token 引用node精确高亮用、tokenName如primary、tokenPath如content.primary。3.2 values 的设计含义一个属性对应多个候选值values是数组而非单值这是理解 collector 的关键。源码中的decomposeValuevalueDecomposer.tsstyled.ts/cssProp.ts 均 import 它负责把复杂表达式拆解成所有可能取值以覆盖三元表达式、逻辑运算等情况background: ${status active ? p p.theme.tokens.background.primary : transparent}这种写法会被拆解成两条valuestoken 引用 literal规则在校验时逐条处理即可无需自己实现 AST 拆解。3.3 context 与 themeBinding作用域感知StyleContext携带file、scopeId与themeBinding。每个声明被创建时都会记录当时有效的 theme 绑定styled.ts。ThemeBindingtypes.ts描述绑定来源——useTheme、styled-callback或css-callback以及本地变量名theme/t/p都可能。3.4 Collector 容器接口StyleCollector是一个极其精简的接口types.tsadd(decl)、getAll()、clear()。底层就是一个数组clear()通过declarations.length 0实现index.ts——所以跨文件复用同一个 collector 前必须调用clear()否则会残留上一个文件的状态。3.5 它故意不捕获什么回到文档的提示collector 对以下内容天然不可见这正是它与createQuasiScanner的根本差异模板字面量 quasis非插值部分中的静态 CSS 文本只以静态文本出现、没有任何动态值跟随的 CSS 属性名注释、空白与格式信息。从实现可验证这一边界styled.ts的extractCssProperty只从每个插值前的那个 quasi 片段里用正则(?:^|[{;])\s*([a-z-])\s*:\s*[^;{]*$抠出属性名styled.ts随后才decomposeValue(expr)处理该插值。若某一行color: #ff0000后面没有${}它根本没有机会进入 collector。4. 两阶段模式Two-Phase Pattern先收集、后校验4.1 为什么必须延迟校验一条 styled 块中的属性和值会横跨多个 AST 节点多个 quasi 多个插值表达式。若在TaggedTemplateExpression进入时就立即做校验你永远只能看到片段。因此 collector 采用延迟校验deferred validation遍历阶段只负责把声明聚合起来真正的业务校验放到Program:exit整个文件解析完、所有声明都已收集齐统一执行。文档给出最小骨架create(context) { if (!shouldAnalyze(context)) return {}; const {collector, visitors} createStyleCollector(context); return { ...visitors, // 展开 collector 的 visitors内含全部提取逻辑 Program:exit() { for (const decl of collector.getAll()) { // 你的校验逻辑写在这里 } collector.clear(); // 必做为下一个文件清理 }, }; }4.2 规范范例use-semantic-token 规则仓库中真正落地的 useSemanticToken.ts 完整演示了这一骨架。它的语义是theme.tokens.*token 只能与它所属语义类别允许的 CSS 属性搭配。核心逻辑分四步// 1) 快速预扫描第一行——见第 5 节 if (!shouldAnalyze(context)) return {}; // 2) 创建 collector校验配置中启用的类别 const {collector, visitors} createStyleCollector(context); // 3) Program:exit 逐个校验 Program:exit() { for (const declaration of collector.getAll()) { validateDeclaration(declaration); } collector.clear(); }validateDeclarationuseSemanticToken.ts展示了消费StyleDeclaration数据的标准姿势值得逐行读先取decl.property.name若以--开头CSS 自定义属性直接跳过——--foo: ${token}这类传给自定义属性的写法不做约束遍历decl.values跳过没有tokenInfo的值用tokenPath查询分类规则findRuleForToken(tokenPath)命中规则后检查rule.allowedProperties是否包含当前属性不包含则 report。若还能通过反向映射PROPERTY_TO_RULE找到该属性应该用哪类 token就给出带建议的报错context.report({ node: tokenNode, // 精确高亮到 token 访问节点 messageId: invalidPropertyWithSuggestion, data: {tokenPath, property: normalizedProperty, suggestedCategory}, });4.3 配置是单一事实来源规则本身几乎没有硬编码的类别知识真正的校验矩阵放在 config/tokenRules.tsinterface TokenRule { name: string; // 人类可读类别名如 content keywords: string[]; // token 路径匹配关键词如 [content, link] allowedProperties: Setstring; // 该类别允许搭配的 CSS 属性集合 }该配置同时承担三份职责文件头注释明确写着SINGLE SOURCE OF TRUTHtoken 检测——tokenPath是否含某类别关键词属性校验——该类别允许哪些属性autofix 建议——由PROPERTY_TO_RULE反向映射从属性找应归属的类别。匹配采用most specific wins最具体者胜如interactive.border.content同时命中 border 与 content 关键词由于 content 层级更深最终归属 content 规则。给这种类别驱动型规则加新能力时通常只需改配置文件如新增一条TokenRule而不用动规则遍历逻辑——这是 SKILL.md Extending an Existing Rule 一节反复强调的设计原则。5. shouldAnalyze进入遍历前的快速预扫描5.1 用法与返回语义文档要求在create()中把shouldAnalyze(context)作为任何使用 style collector 或分析 Emotion 模式的规则的第一行。它的作用不是精确判定而是正则级预筛文件明显不含 Emotion/styled 模式时直接返回空监听器为成千上万个无关 TS/TSX 文件节省 AST 全量遍历的开销。5.2 底层判定逻辑源码 extractor/index.ts 暴露了完整规则import 命中或用法命中任一即可。import 命中源码文本包含emotion/styled或emotion/react用法命中正则useTheme调用、styled[.(]含styled.div/styled(/styled、 css[({]css\/css(/css{、或 JSX 属性css/style。代码注释特别解释了同时检查 import 与 usage的原因有 import 但无实际用法的文件虽然少见但确实存在例如 re-export 文件没有直接 import 却在使用styled/css的文件也可能存在这些名称来自别处注入。shouldAnalyze允许误报false positive——宁可多做几次无谓的遍历也要保证不漏掉真正需要分析的文件。注意它做的是源码文本的includes/正则测试不是 token 级 AST 分析因此非常廉价。6. 最常见的坑Collector 与静态文本的边界style-collector-guide.md用一整节强调头号错误规则需要分析静态 CSS 文本时误用了createStyleCollector。const Box styled.div color: #ff0000; // ← quasi 中的静态文本collector 看不到它 background: ${p p.theme.tokens.background.primary}; // ← 这个才会被捕获 ;行为差异非常直观第一行的color: #ff0000是 quasis 里的静态文本没有任何插值表达式紧随其后——collector采集不到第二行由于存在${...}插值会被采集为{property: background, values: [token 引用]}。因此如果你的规则要检测的是文本本身的模式——裸十六进制颜色、嵌套选择器、静态属性名——请使用静态文本分析Template Text Analysis archetype见 rule-archetypes.md 的决策表而不是 Style Collector。两条路线的选择可归纳为一个简单问题**你想分析这段 CSS 写死了什么还是这个插值/ token 被用于哪个属性**前者走 quasi 文本扫描后者才走createStyleCollector。在 style-collector-guide.md 里作者还特别处理了伪选择器干扰属性提取正则要求属性名出现在{、;或行首之后从而避免把a:hover中的a误认成属性。7. 配套工具全景如何组合出完整的样式规则Style Collector 只是 ast 工具链的一员。依据 SKILL.md 第 2 步的共享工具表写规则时可复用的能力如下路径均以仓库根目录换算工具仓库内真实路径用途createStyleCollectorast/extractor/index.ts采集 CSS-in-JS 动态值声明本文主题shouldAnalyzeast/extractor/index.ts快速预筛跳过无 Emotion 用法的文件getStyledCallInfoast/utils/styled.ts把 styled/css 调用归类为 element / component / cssnormalizePropertyNameast/utils/normalizePropertyName.tsCSS 属性名归一化如驼峰转 kebab-casedecomposeValueast/extractor/valueDecomposer.ts把复杂表达式拆成所有可能取值createThemeTrackerast/tracker/theme.ts追踪useTheme()与回调式 theme 绑定createImportTrackerast/tracker/imports.ts解析本地名从何处 import配合 JSX 结构类规则createStyleCollector内部已经替你组装了 theme tracker 与三个 extractor普通规则无需直接接触它们。但如果你的规则还要做JSX 结构约束如restrict-jsx-slot-children则需自行引入createImportTracker 递归 JSX 树遍历——这两类需求通常不会同时出现在一条规则里。7.1 Theme Tracker 内部做了什么theme.ts 值得单独认识因为它是 collector 能够识别p就是 theme的原因。它跟踪的绑定形态包括import {useTheme} from emotion/react同时记住本地别名const theme useTheme()/const t useTheme()const {tokens} theme对象解构后tokens.xxx也算 theme 绑定回调参数绑定(theme) ...、(p) p.theme经registerCallbackBinding注册到当前作用域。作用域管理使用显式的scopeStack每进入一个ArrowFunctionExpression/FunctionExpressionpush 一个新 scopeId退出时把该作用域注册的绑定从集合中删除theme.ts从而保证getActiveBinding()在任意时刻返回的确实是当前作用域可见的 theme。8. 端到端实践从 archetype 到一条可用的新规则8.1 编写步骤回顾判断意图若你要做的是按 CSS 属性校验 token/值的使用锁定Property validationarchetype加载 style-collector-guide.md若是检测静态文本则切到 Template text analysis不要用 collector。检查共享工具见上表已有逻辑不要重写。创建文件规则static/oxlint/eslintPluginScraps/src/rules/$RULE_NAME.ts测试static/oxlint/eslintPluginScraps/src/rules/$RULE_NAME.spec.ts命名约定SKILL.md规则名kebab-case动词-名词如no-token-import、use-semantic-token导出名camelCaseuseSemanticToken文件名与规则名一致。套用规则模板基于ESLintUtils.RuleCreator.withoutDocs在create()第一行放shouldAnalyze预筛。测试驱动用RuleTester写 valid/invalid 用例跑pnpm test-ci static/oxlint/eslintPluginScraps/src/rules/$RULE_NAME.spec.ts8.2 注册与启用把规则加入 rules/index.ts 的rules映射key 为 kebab-case 规则名在插件配置的name: plugin/sentry/scraps块中启用键名为sentry/scraps/$RULE_NAME可取error或[error, {options}]SKILL.md 第 4 步规则若声明为 fixable每个 invalid 测试用例必须带output字段即期望的修复后代码这是 fixable 规则的硬性要求。8.3 关于 autofix 的边界建议SKILL.md 的默认立场是尽量实现 autofix但明确列出不可自动修复的情形其中与本主题直接相关的一条是修复需要 AST 之外的类型信息。CSS-in-JS 动态值属于典型的类型不敏感但语义敏感数据——例如把 token 从一个类别换到另一个类别需要理解theme.tokens的类型结构仅凭 AST 无法安全判断——因此这类规则往往只 report 并给出建议文本而不提供自动改写。8.4 相关测试与延伸阅读现有规则的可参考实现useSemanticToken.ts 及其.spec.ts另有 noTokenImport.ts、noDoubleDollarInterpolation.ts 可从不同角度理解插件惯例。archetype 总览与更多示例rule-archetypes.md。若要为规则增加可配置项schema参考 references/schema-patterns.md。9. 关键文件速查文件仓库根目录相对路径作用style-collector-guide.md本文讲解的核心关联文档SKILL.md新规则编写总流程与共享工具表rule-archetypes.md规则意图 → 技术路线的决策表ast/extractor/index.tscreateStyleCollector与shouldAnalyze实现ast/extractor/types.tsStyleDeclarationIR 的权威类型定义ast/extractor/styled.tsstyled/css 标签模板与对象语法提取ast/extractor/cssProp.tsJSXcssprop 提取ast/tracker/theme.tstheme 绑定追踪config/tokenRules.tstoken→属性 校验矩阵单一事实来源rules/useSemanticToken.tsStyle Collector 的规范使用范例总结Style Collector 是 Sentry 设计系统 lint 基础设施中最关键的抽象之一它把从 CSS-in-JS 里抓出带动态值的属性声明这件重复劳动下沉为共享工具让规则作者专注业务判断。写这类规则时只需记住三个要点——预扫描先行shouldAnalyze、遍历期只收集、Program:exit统一校验并clear()同时守住一条边界——它只看得见插值动态值静态 CSS 文本请交给 quasi 文本扫描。以 useSemanticToken.ts 为模板、以 tokenRules.ts 为配置载体即可用最小的样板成本扩展出新的属性校验规则。【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
