eslint-plugin-drizzle 0.2.2 解析:修复嵌套对象中 drizzleObjectName 检测问题的技术细节
【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm
导读
本文围绕 eslint-plugin-drizzle 0.2.2 版本的核心修复——「当drizzleObjectName指向嵌套对象时,规则检测不再失效」——展开,结合仓库源码与测试用例,深入剖析enforce-delete-with-where与enforce-update-with-where两条规则的工作机制、drizzleObjectName选项的三种取值形态,以及嵌套对象(如this.dataSource.db.delete())场景下的成员表达式解析原理。读完本文,你将能够准确理解该插件在 0.2.2 版本中解决了什么问题、修复如何落地,以及在自己的项目中如何正确配置这两条防止危险 SQL 的规则。
一、0.2.2 版本变更:一条修复的含义
0.2.2 变更日志 只记录了一条变更:
fix: Correct detection of
drizzleObjectNamewhen it's a nested object
这条修复要解决的是:当 Drizzle 数据库实例不是顶层变量db,而是被封装在嵌套对象中(例如this.dataSource.db、this.database.db、this.getDataSource().db),并且用户通过drizzleObjectName配置指定了内部对象名时,规则无法正确识别该调用来自 Drizzle 对象,从而导致误报(把非 Drizzle 的delete()/update()当作 Drizzle 调用)或漏报(真正的 Drizzle 调用未被拦截)。
要理解这条修复,必须先弄清楚drizzleObjectName选项与成员表达式(MemberExpression)解析的实现细节。
二、两条规则与 drizzleObjectName 选项
2.1 规则背景:防止无 WHERE 的全表删除与全表更新
eslint-plugin-drizzle 目前提供两条规则(见 src/index.ts):
enforce-delete-with-where:强制.delete()必须搭配.where(),防止误删全表;enforce-update-with-where:强制.update().set()必须搭配.where(),防止误改全表。
两条规则的元数据声明一致:type: 'problem'、fixable: 'code',并注册了一个可选的drizzleObjectName配置项,其类型为string | string[],见 enforce-delete-with-where.ts 与 enforce-update-with-where.ts。
2.2 drizzleObjectName 的三种形态
drizzleObjectName用于告诉规则:代码中哪个标识符才是 Drizzle 数据库实例,从而避免把用户自定义类(同样有delete()/update()方法)的调用误判为违规。其判定逻辑集中在 src/utils/options.ts 的isDrizzleObjName函数中:
- 字符串形态:
typeof drizzleObjectName === 'string'时做精确相等比较,例如{ "drizzleObjectName": "db" }; - 数组形态:传入
string[]时,只要命中数组中任一名称即视为 Drizzle 对象;特别地,空数组[]等价于「不限制」,任何对象名都会被命中(drizzleObjectName.length === 0直接返回true),这也是默认行为; - 默认值:两条规则的
defaultOptions均为[{ drizzleObjectName: [] }],意味着不配置时规则对任意对象上的delete()/update()都生效。
{ "rules": { "drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db"] }], "drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": "db" }] } }三、修复的核心:嵌套对象的成员表达式检测
3.1 修复前的缺陷
isDrizzleObj(src/utils/options.ts)负责判断一个MemberExpression节点是否来自 Drizzle 对象。它逐层检查node.object的类型:
Identifier:取node.object.name与配置比对;MemberExpression:取node.object.property.name与配置比对;CallExpression:取 callee 的name或property.name与配置比对。
问题在于:当 Drizzle 对象以多级嵌套形式出现时(如this.dataSource.db),isDrizzleObj只检查了第一层成员表达式(this.dataSource)的属性名dataSource,而未递归到内层db。此时如果用户配置的是drizzleObjectName: ["db"],dataSource不匹配,规则就会认为这不是 Drizzle 调用,从而漏报真正的db.delete()/db.update().set()危险操作。
3.2 修复后的检测流程
0.2.2 修复后,检测链按「从外到内」的顺序逐层提取属性名,只要任意一层命中配置的drizzleObjectName,就判定为 Drizzle 对象。以this.dataSource.db.delete()为例:
- 外层成员表达式为
this.dataSource.db(调用delete的接收者),其property.name为db; isDrizzleObj命中db与配置匹配,判定为 Drizzle 对象,触发规则;- 同样地,
this.getDataSource().db、this.database.getDatabase()等链式嵌套也能被正确识别。
这一点在 delete.test.ts 中有直接印证:this.dataSource.db.delete({})在drizzleObjectName: ['db']配置下被报告,且错误消息中的drizzleObjName被解析为完整的this.dataSource.db。
3.3 错误消息中的对象路径解析
修复同时让错误提示更精准。规则在context.report中通过resolveMemberExpressionPath(src/utils/ast.ts)还原出完整的调用链文本,用于构造类似下面这条可读性极强的提示(见 enforce-update-with-where.ts):
Without
.where(...)you will update all the rows in a table. If you didn't want to do it, please usethis.dataSource.db.update(...).set(...).where(...)instead.
resolveMemberExpressionPath会沿 AST 向上遍历,把MemberExpression、CallExpression、Identifier、ThisExpression逐层拼接为字符串。从测试用例可以看到它支持的各种形态:
| 代码形态 | 解析出的 drizzleObjName |
|---|---|
db.update({}).set() | db |
getDatabase().update({}).set() | getDatabase(...) |
this.dataSource.getDatabase(arg1, arg2).update({}).set() | this.dataSource.getDatabase(...) |
this.getDataSource().db.update({}).set() | this.getDataSource(...).db |
这些断言分别出现在 update.test.ts 与 delete.test.ts 中,覆盖了this嵌套、方法链返回对象、带参数调用等多种真实编码场景。
四、where状态跟踪:规则的触发条件细节
两条规则都通过模块级变量lastNodeName记录「上一次访问过的成员属性名」,以判断.where()是否已经出现:
- delete 规则(enforce-delete-with-where.ts):当访问到
delete属性、且lastNodeName !== 'where'时报告违规; - update 规则(enforce-update-with-where.ts):当访问到
set属性、其外层是update(...)调用、且lastNodeName !== 'where'时报告违规。
因此合法的链式调用db.update().set().where()与db.delete().where()不会触发规则,而db.update().set()、db.delete()、甚至db.update({}).set(未调用)都会被标记为错误——对应测试中的 valid/invalid 用例。注意lastNodeName是模块级状态,两条规则分别在各自模块内维护,互不干扰。
五、安装与配置实战
5.1 安装
在项目根目录执行(详见 readme.md):
# npm / yarn / pnpm / bun 任选其一 npm install eslint eslint-plugin-drizzle npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser@typescript-eslint/parser是必要的——规则的RuleTester测试即基于它运行(见 update.test.ts),生产环境同样依赖它解析 TypeScript 语法。
5.2 单条规则配置(.eslintrc.yml)
root: true parser: '@typescript-eslint/parser' parserOptions: project: './tsconfig.json' plugins: - drizzle rules: 'drizzle/enforce-delete-with-where': "error" 'drizzle/enforce-update-with-where': "error"5.3 推荐配置:全量 / 推荐
插件导出了all与recommended两套预设配置(src/configs/recommended.ts),二者目前等价,均将两条规则设为error:
root: true extends: - "plugin:drizzle/recommended" parser: '@typescript-eslint/parser' parserOptions: project: './tsconfig.json' plugins: - drizzle5.4 减少误报:指定 drizzleObjectName
当项目中存在自带delete()/update()方法的类时,不配置drizzleObjectName会把它们的调用也判为违规。通过配置仅让 Drizzle 对象触发规则:
{ "rules": { "drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db"] }], "drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": ["db"] }] } }配置后的行为差异如下(来自 readme.md 与 delete.test.ts):
class MyClass { public delete() { return {} } } const myClassObj = new MyClass(); myClassObj.delete(); // 配置了 drizzleObjectName 后不再误报 this.database.db.delete(); // 嵌套对象:0.2.2 修复后可被正确识别并报错六、从版本演进看修复脉络
将 0.2.2 放入版本序列中可以更清楚地看到这条修复的价值:
- 0.2.0(变更日志):首发版本,提供两条规则;
- 0.2.1(变更日志):完善 README、调整错误文案;
- 0.2.2(变更日志):修复嵌套对象下的
drizzleObjectName检测,补齐了成员表达式递归识别的能力; - 0.2.3(变更日志):进一步覆盖「
drizzleObjectName来自函数返回值」的场景,并将isDrizzleObjName提取为公共函数消除重复代码。
由此可见,0.2.2 是插件检测能力从「仅识别顶层变量」走向「支持嵌套对象」的关键一步,为后续对函数返回对象形态的支持奠定了基础。
结语
eslint-plugin-drizzle 0.2.2 的这条单行变更日志,背后是对 MemberExpression 递归解析逻辑的一次实质增强:它让drizzleObjectName在this.dataSource.db、this.getDataSource().db等嵌套对象场景下能够被正确命中,既避免了危险的无where全表操作漏网,也减少了自定义类方法调用的误报。对于在大型项目中将 Drizzle 实例封装在 Service、DataSource 等容器对象中的团队而言,升级到 0.2.2 及以上版本并配合drizzleObjectName配置,是构建「无 where 即报错」安全底线的高性价比方案。
【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考