写过 TypeScript 的人,今天才知道某个写法三年了都是错的——Oxlint 1.79.0 把这件事从根上翻了
写过 TypeScript 的人都习惯在可能为 null 的值后面加 !,告诉自己「这里肯定不是 null,TS 你别报错」。但有些写法加了比不加还危险——比如 a! == b,看起来像「a 不等于 b」,实际上是一个非空断言加一个相等判断,两个操作符叠在一起,reviewer 瞪三眼才能确认没看错。Oxlint 1.79.0 今天把这件事从根上说了清楚。
第一个坑:a! == b 看起来像 a !== b
非空断言 ! 放在 == 前面,视觉上几乎和 !== 重合。在斜体渲染或者某些编辑器主题下,两个字符挨在一起,几乎分不出哪个是 ! 哪个是 =。
// 危险写法,Oxlint 1.79.0 会报错
const isMatch = value! == expected;
const isDifferent = count! !== total;
const hasKey = map! in anotherMap; // 更隐蔽
const isInstance = obj! instanceof SomeClass;
// 正确写法:要么加括号让意图清晰,要么把非空断言放在表达式外面
const isMatch = value == expected;
const isMatch2 = (value)! == expected; // 括号隔离了 !
const isMatch3 = (value!) == expected; // 同样清晰
这条规则叫 no-confusing-non-null-assertion,来自 TypeScript ESLint 社区,现在 Oxlint 1.79.0 原生实现了。它会建议你去掉不必要的 ! 或者用括号把断言部分包裹起来,让两个操作符的边界一目了然。
第二个坑:import { foo as foo } 写了等于没写
另一个新规则是 no-useless-rename。这个坑更隐蔽,也更常见:
// 错误写法,as 两边名字一样,Oxlint 1.79.0 会报
import { foo as foo } from 'module';
export { bar as bar };
const { x: x, y: y } = someObject;
// 正确写法:直接写原名
import { foo } from 'module';
export { bar };
const { x, y } = someObject;
这种写法通常出现在以下场景:复制粘贴时忘了改名字、IDE 自动补全时顺手保留了 as 关键字、或者重构时改名漏了 as。表面上是风格问题,但这种代码在代码库里出现往往意味着这里发生过复制粘贴——而复制粘贴的地方常常埋着其他的 bug。Oxlint 的 autofix 会直接帮你删掉这个多余的 as。
第三个:按引用解析全局变量,减少误报
1.79.0 还有一个值得注意的改进:resolve globals by reference,改变了 Oxlint 解析全局变量的方式。
之前的版本按变量名匹配全局定义,容易把用户自定义的局部变量误认为全局变量,导致不该触发的规则被触发。1.79.0 改用引用追踪(reference-based resolution),只有真正引用了全局作用域的地方才会被当作全局变量处理。这个改动直接减少了误报数量,特别是那些在局部作用域里定义了和全局变量同名函数的项目。
怎么升级
升级到最新版只需要一行命令:
npm install -D oxlint@latest
如果你已经在用 oxlintrc 配置文件,新规则默认包含在 typescript 相关 preset 里,不需要额外配置。如果想显式开启,在配置里加两条规则即可:
{
"rules": {
"typescript/no-confusing-non-null-assertion": "error",
"typescript/no-useless-rename": "error"
}
}
两个规则都支持 autofix,编辑器保存时直接帮你修。
为什么这不只是风格规则
很多人看到「非空断言规则」第一反应是「这不就是格式问题吗」——但 a! == b 和 a !== b 之间的差异是逻辑性的,不是风格性的。前者在你确认 value 不为 null 时才是对的,后者是一个恒真/恒假判断。如果你加 ! 是因为「TS 报错了没办法才加的」,而不是真正确认了语义,那这条规则就是在帮你发现你埋的雷。no-useless-rename 同理,重复名字往往意味着这里有复制粘贴的痕迹,而复制粘贴的区域是 bug 的高发地带。
Oxlint 2026 年的加速计划正在把以前只有大型企业 ESLint 配置才能跑得动的规则,慢慢变成每个人按一下保存就能触发的实时反馈。Rust 底层的并行处理让这一切在大型 monorepo 里也能在亚秒级完成。
下一步: 跑一下 npx oxlint --disable-all -D typescript/no-confusing-non-null-assertion -D typescript/no-useless-rename . 看看你的代码库里有没有这两个坑,有的话加上 --fix 直接修掉。
评论区
登录后可评论。