
【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载本篇围绕 gsd-core 仓库自研的本地 ESLint 规则local/no-source-grep展开讲解它如何拦截用readFileSync读取源码文件后再对其结果做字符串搜索的测试反模式。重点剖析本次变更PR #2982新增的变量绑定追踪能力——即使 source-grep 隐藏在中间变量或解析器包装函数之后规则依然能够识别并让对应测试失败同时给出规则的判定细节、豁免注释机制、仓库配置方式与修复建议读完即可在同类项目中复现这套测试不许 grep 源码的防线。一、问题背景测试里的 source-grep 反模式在测试代码中开发者偶尔会写出这样的断言用fs.readFileSync()读取仓库的某个源码文件然后在返回的文本上调用.includes()、.match()等字符串方法以验证源码里写了某个东西const raw readFileSync(path.join(__dirname, .., src, foo.cts), utf8); assert.ok(raw.includes(someInternalFunction));这种模式在本仓库被命名为source-grep它的隐患在于断言极其脆弱源码只要发生格式调整、注释变动、标识符重命名测试就会无意义地挂掉或悄然变绿语义错位测试想验证的往往是模块暴露了什么能力正确做法是require()该模块并直接断言其导出行为而不是在源码文本上做正则/子串匹配难以审查文本匹配的结果不可被静态分析追踪一旦源码被重构测试不会给出任何指向性提示。本仓库的变更记录.changeset/archived/zesty-moles-forage.mdtype: AddedPR #2982正是针对这一反模式的加固将no-source-greplint 规则扩展使其能够捕获var-binding形式的readFileSync(...).includes()模式——即先把读取结果绑定到变量、再在变量上调用文本搜索方法的情况并且保证当 source-grep 隐藏在解析器包装parser wrapper之后时测试依然会失败。二、规则判定核心什么才算 source-grep规则的完整实现位于 eslint-rules/no-source-grep.cjs其文档描述为Disallow reading source .cjs/.cts/.js/.mjs/.mts/.ts files with readFileSync and then doing text search on the result即禁止用readFileSync读取源码文件后对结果做文本搜索。判定分两层1. 被禁的文本搜索方法TEXT_METHODS规则内置了一个明确的方法名单见TEXT_METHODS集合includes match matchAll startsWith endsWith indexOf search split replace只要readFileSync的结果无论直接内联还是绑定到变量上调用了以上任一方法就会被标记为违规。2. 正则形态的搜索regex.test()/regex.exec()除字符串方法外规则还覆盖了用正则去搜源码文本的变体对应 epic #3464 phase 8regex.test(tracked)、/literal/.test(tracked)、regex.exec(tracked)、/literal/.exec(tracked)。此时被追踪的不是正则接收者而是参数——只要参数携带了追踪中的源码文本即判违规两种形态共享同一套接收者/参数分类逻辑。3. 什么样的读取才算读源码isSourceReadFileSync()对一个CallExpression做两项检查调用形态裸的readFileSync(...)或fs.readFileSync(...)/xxx.readFileSync(...)路径特征looksLikeSourcePath()路径表达式最终必须以源码扩展名结尾.cts、.mts、.mjs、.cjs、.js、.ts扩展名匹配按长度优先排列避免.cts被更短的.js分支部分命中路径中必须出现源码目录指示符之一bin、lib、gsd-core、hooks、srchooks目录是 #3545 / epic #3464 phase 7 起被纳入的。4. 路径参数的一跳解析resolveOneHopText若readFileSync的路径参数是一个裸标识符规则会回溯一跳到它的VariableDeclarator初始值再做文本分类。这样const p path.join(__dirname, .., src, x.cjs); readFileSync(p)与内联写法readFileSync(path.join(...))会被一视同仁地识别。三、变量绑定追踪从 readFileSync 到 includes 的传递链条PR #2982 的核心贡献就是把规则的追踪能力从单行内联扩展为跨变量绑定的传递追踪。1. 基于真实作用域的身份解析而非名字字符串规则通过 ESLint 的sourceCode.scopeManager/getScope拿到真正的Variable对象来解析标识符身份resolveVariable()、buildIdentifierVariableMap()而不是按变量名字符串匹配。这意味着不同作用域里同名的绑定永远不会与被追踪的变量混淆——即使某个无关作用域里有一个同名的raw也不会被误判或漏判。2. hop 机制与传递深度上限规则给每个被追踪的变量记录一个hop 值hop 1变量直接绑定在 sourcereadFileSync()的结果上种子阶段在Program:exit时对收集到的VariableDeclarator/AssignmentExpression统一注入hop n变量从 hopn-1的父变量推导而来const b f(a)、b a等。推导在Program:exit内以fixpoint 循环方式迭代完成每个变量最多入账一次因此必然终止。深度被MAX_TRANSITIVE_HOPS 3硬性封顶——链条超过 3 跳即放弃这是有意的、文档化的盲点见 40-design.md Known limits而非缺陷。3. 形状收窄什么结果不再携带源码文本追踪并非无脑传播规则按表达式形状精确收窄trackedInfo()不传播的形状结果确定不含文本返回nullMemberExpression属性读取如raw.length——这正是历史上出现过误报的形状UnaryExpression!x、typeof x、-x等数组/对象字面量及解构——这是文档化的刻意盲点比较/算术等除以外的二元运算调用Number()/parseInt()/parseFloat()/Boolean()NON_PROPAGATING_CALLEE_NAMES、Array.isArray()在被追踪值上调用NON_PROPAGATING_METHODSindexOf、lastIndexOf、search、charCodeAt、codePointAt、localeCompare、includes、startsWith、endsWith、test——注意直接调用这些方法本身即构成违规但其返回值不再继续被追踪避免const ok raw.includes(x); ok.foo()被误当成源码文本继续追。继续传播的形状replace/replaceAll/slice/substring/trim/toLowerCase/split/concat等字符串变换PROPAGATING_STRING_METHODS、模板字符串${a}、字符串拼接、await、三元表达式的分支等。4. 保守默认这正是解析器包装防线对应 #2982对于无法明确识别的调用形状如把追踪值作为参数传给任意函数const b strip(a);、parse(raw)等规则选择走walkForTrackedInfo()的保守兜底默认继续传播而不是静默丢弃——因为调用方/运算符完全可能返回从追踪值派生出的文本。宁可冒一次更罕见的误报也绝不放过一个真实的正例。这一保守传播的设计正是 #2982 所要求语义的落地source-grep 被藏进一层解析器包装把读取结果先交给某个解析/变换函数再搜索时追踪链条依然不断测试依然失败。四、站点级豁免allow-test-rule注释规则保留了一个受控的逃生舱——站点级豁免注释// allow-test-rule: reason (#NNN)其语义要点源码注释有完整阐述站点级而非文件级一个标记只抑制它紧邻的违规同一行或位于其上、中间只有空行/注释行不会波及整个文件。判定由isSuppressedAt()实现标记行与违规行距离必须 ≤MAX_MARKER_LOOKAHEAD_LINES 8且两者之间每一行都必须是空行或纯注释行纯度检查保证标记不能漂移到 40 行外一个无关的违规点去这正是 test-matrix.md row 4 所封堵的缺陷。read/search 两侧皆可判定同时检查搜索调用所在行与readFileSync()原始读取行readLineOf映射把原始读取行沿传递链一路携带——把标记放在读取处直觉位置和放在搜索调用处效果相同这是对抗性审查的修复epic #3464 phase 4。标记识别基于 AST 注释值MARKER_COMMENT_RE /allow-test-rule:\s*\S/作用在注释节点剥离分隔符后的value上而非原始源码文本collectMarkerAndCommentLines()一次性算出标记行集合与纯注释行集合供判定复用。这两个关键常量MAX_MARKER_LOOKAHEAD_LINES、MARKER_COMMENT_RE以及两个纯函数collectMarkerAndCommentLines、isSuppressedAt都被显式导出module.exports.*作为唯一事实来源供 scripts/lint-allow-test-rule-refs.cjs有效豁免计数器等外部消费者复用避免两处各自实现判定算术导致生成性修复漂移——这是本仓库曾经踩过的坑。五、接入方式与配置1. 本地插件注册规则通过本地插件挂入 ESLint flat config。在 eslint.config.mjs 中import noSourceGrep from ./eslint-rules/no-source-grep.cjs; const localPlugin { rules: { no-source-grep: noSourceGrep, // ... 其余 20 条本地 AST 规则 }, };配置文件中以local/no-source-grep: error在多个相关代码块上启用覆盖测试文件等目标作为 CI 与 IDE 的硬性门禁。2. 演进背景从正则脚本到 AST 规则ADR 452 记录了这条规则的出身它最初是scripts/lint-no-source-grep.cjs这类基于正则表达式的行扫描脚本对原始源码文本做模式匹配。这种方案存在结构性弱点无法区分注释与真实代码、无法理解作用域与变量绑定、容易产生误报/漏报。仓库随后整体迁移到 ESLint flat configeslint ≥ 9typescript-eslint 本地 AST 规则插件把 import-graph、Node API、测试严谨性等检查统一收口到 ESLint 这一个执行点正则脚本随之退役。早期迁移时旧的// allow-test-rule:注释曾要求改写成 ESLint 标准的// eslint-disable-next-line local/no-source-grep -- reason而当前源码#3508 / epic #3464 之后已恢复对allow-test-rule语法的原生识别两种豁免途径并存。3. 诊断开关neutralizeSuppression规则 schema 接受一个neutralizeSuppression: boolean选项。开启后每个候选违规都会被上报无视豁免标记并额外附上携带原始读取行号的诊断消息——本仓库没有任何配置会传这个选项它纯粹是给有效豁免计数器脚本做全量站点盘点用的一次真实 ESLint 遍历枚举完整站点清单再用导出的isSuppressedAt对照真实标记分类对线上 lint 行为零影响。六、测试验证与已知边界规则的回归测试集中在 tests/eslint-rules.test.cjs 与 tests/lint-allow-test-rule-refs.test.cjs后者专门覆盖有效豁免计数脚本与规则导出判定的一致性。配套的 test-matrix 与 40-design 文档见.gsd/phase/feat-3677-quick-batch-hardening-acceptance/下的 50-test-matrix.md、40-design.md以表格形式枚举了各判定行并对以下文档化盲点作出明确声明超过MAX_TRANSITIVE_HOPS的传递链如a → b → c → d四跳通过AssignmentExpression绑定而非VariableDeclarator初始化器来构造的路径参数路径解析只回溯一跳且只看init数组/对象字面量装载与解构取回。这些都是刻意选择的取舍——用可控的漏网率换取规则的简单性与可证明的终止性。七、违规修复建议当local/no-source-grep报错时首选修法是放弃文本断言直接加载模块并断言其导出行为规则错误消息也如此提示// 错误读源码再 includes const raw readFileSync(path.join(__dirname, .., src, foo.cts), utf8); assert.ok(raw.includes(someInternalFunction)); // 正确加载模块并断言真实行为 const mod require(../src/foo.cts); assert.equal(mod.someInternalFunction(), expected);确属无法避免的场景例如校验某个生成文件的占位符格式才使用站点级豁免注释// allow-test-rule: reason (#NNN)或// eslint-disable-next-line local/no-source-grep -- reason且必须让注释紧贴被豁免的调用确保豁免范围始终是这一处而不是整个文件。总结local/no-source-grep是 gsd-core 测试严谨性防线中具有代表性的一条 AST 规则。PR #2982 把它的能力从单行内联识别推进到基于真实作用域的变量绑定传递追踪配合保守传播兜底即便 source-grep 被藏在解析器包装之后也无法蒙混过关再叠加站点级豁免注释与配套的豁免计数脚本形成了一条默认拦截、按点放行、可审计的完整治理闭环。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐eslint-plugin-unicorn 的 no-unreadable-new-expression 规则让 new 表达式不再难读eslint plugin unicorn 的 no unreadable new expression 规则让 new 表达式不再难读 no unreadaLint代码质量eslint-plugin-react 的 no-is-mounted 规则彻底告别 isMounted 反模式eslint plugin react 的 no is mounted 规则彻底告别 isMounted 反模式 导读 react/no is mounted开发工具代码质量静态分析深入解读 eslint-plugin-unicorn 的 no-useless-coercion 规则快照测试与类型推断原理深入解读 eslint plugin unicorn 的 no useless coercion 规则快照测试与类型推断原理 本篇技术指南以 no useleLint代码质量上一篇开源项目推荐Magisk on WSA - 在Windows子系统中畅享安卓魅力下一篇flow_matching损失函数完全解析从广义KL损失到实际应用场景创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考