在ESLint v8.14.0中,我贡献了一个新的核心规则,叫做no-constant-binary-expression,它能够检测到的各种微妙而有趣的bug让我感到惊讶。
在这篇文章中,我将解释该规则的作用,并分享一些它在流行的开源项目中检测到的真实错误的例子,如Material UI、Webpack、VS Code和Firefox,以及它在Meta内部发现的一些有趣的错误。我希望这些例子能说服你在你工作的项目中尝试启用这个规则
no-constant-binary-expression 是做什么的?
该规则检查比较(==,!==, 等),其结果在运行时不能改变,以及逻辑表达式(&&,??,|| ),这些表达式要么总是短路,要么从不短路。
例如:
+x == null将永远是假的,因为 将胁迫 成为一个数字,而一个数字永远是空的。+x{ ...foo } || DEFAULT将永远不会返回 ,因为对象永远是真实的。DEFAULT
这两个例子都是表达式的例子,看起来它们会影响程序的评估方式,但实际上不会。
这个规则最初只是为了检测不必要的空值检查。然而,随着我的努力,我意识到无用的空值检查只是一个更广泛类别的一个特例:无用的代码。最终,我明白了:开发人员并不打算写无用的代码,而不符合开发人员意图的代码从定义上来说就是一个bug。因此,任何你能发现的无用代码都是一个bug。
当我在Meta公司的代码库中运行第一版规则时,这一认识得到了证实,它检测到了各种微妙而有趣的bug,这些bug都通过了代码审查。
在现实世界中发现的bugno-constant-binary-expressions
在这一节中,我将分享该规则所能捕获的一些类型的错误。每一种都包括至少一个在流行的开源项目中发现的具体例子。我选择在这里包括真实的例子,并不是要羞辱任何人或任何项目,而是要让大家知道,这些错误是任何团队都容易犯的。
混淆运算符优先级
本规则发现的最常见的一类错误是开发人员误解了运算符的优先级,特别是单数运算符,如! 、+ 和typeof :
if (!whitelist.has(specifier.imported.name) == null) {
return;
}
混淆了?? 和|| 的优先级
当试图定义默认值时,人们会对像a === b ?? c 这样的表达式感到困惑,并认为它将被解析为a === (b ?? c) 。而实际上,它将被解析为(a === b) ?? c :
shouldShowWelcome() {
return this.viewModel?.welcomeExperience === WelcomeExperience.ForWorkspace ?? true;
}
来自VS代码
旁白。观察到开发者经常被运算符优先级所迷惑,激发了我对VS Code扩展的实验,以直观地阐明优先级是如何被解释的。
期待对象通过值进行比较
来自其他语言的开发者,在这些语言中,结构是通过值而不是通过引用进行比较的,他们很容易陷入这样的误区:认为他们可以通过与一个新创建的空对象进行比较来测试一个对象是否为空。当然,在JavaScript中,对象是通过引用进行比较的,没有任何值可以等于一个新构造的对象字面。
在这个例子中,hasData 将永远被设置为真,因为data 不可能在引用上等于一个新创建的对象:
hasData = hasData || data !== {};
期待空对象是false 或null
另一个常见的JavaScript错误类别是期望空对象为nullish或falsy。对于来自Python这样的语言的人来说,这可能是一个容易犯的错误,因为在Python中,空的列表和字典是虚假的。
const newConfigValue = { ...configProfiles } ?? {};
它是>= 还是=> ?
我只见过一次这个特殊的错字,但我想把它包括进来,因为它是一个很好的例子,说明这个规则可以捕捉到意想不到的错误类型。
在这里,开发者想测试一个值是否大于或等于零(>= 0 ),但不小心颠倒了字符的顺序,创建了一个箭头函数,返回0 && startWidth <= 1!
assert(startWidth => 0 && startWidth <= 1);
以上五类错误并不详尽。当我最初在Meta公司的(非常)大的monorepo上运行这个规则的第一个版本时,它发现了500多个问题。虽然许多问题属于上述类别,但也有许多其他有趣的错误。一些亮点包括:
- 认为
||,允许进行集合操作。states.includes('VALID' || 'IN_PROGRESS') - 认为原始函数会通过空值。
Number(x) == null - 不知道基元构造函数会返回盒式基元。
new Number(x) === 10
我从来没有想过要单独对这些具体的问题进行检查,但是通过简单地试图识别任何 "无用 "的东西,我们就能够发现并纠正它们。
结论
正如你现在看到的no-constant-binary-expression ,它能够检测各种不同类型的错误。该规则之所以能做到这一点,并不是因为它的程序设计是为了寻找那些特定的问题,而是因为所有这些错误都有一个共同点:它们表现为无用的代码。因为开发人员一般都不打算写无用的代码,所以检测无用的代码一般都会导致检测出bug。
如果你觉得这些例子很有说服力,请考虑在你的ESLint配置中启用no-constant-binary-expression。
// eslintrc
module.exports = {
rules: {
// Requires eslint >= v8.14.0
"no-constant-binary-expression": "error"
}
}
如果你这样做了,并且它发现了bug,我很想听听你的意见。