有趣的错误被 no-constant-binary-expression 捕获

一条能捕捉到出人意料的各种逻辑错误的新规则。

ESLint v8.14.0 中,我贡献了一个新的核心规则,名为 no-constant-binary-expression,它让我惊讶的是,它能够检测出各种微妙且有趣的错误。

🌐 In ESLint v8.14.0 I contributed a new core rule called no-constant-binary-expression which has surprised me with the wide variety of subtle and interesting bugs it has been able to detect.

在这篇文章中,我将解释该规则的作用,并分享一些它在流行开源项目中检测到的真实漏洞实例,如 Material UI、Webpack、VS Code 和 Firefox,以及它在 Meta 内部发现的一些有趣漏洞。我希望这些例子能说服你在你参与的项目中尝试启用该规则!

🌐 In this post I’ll explain what the rule does and share some examples of real bugs it has detected in popular open source projects such as Material UI, Webpack, VS Code, and Firefox as well as a few interesting bugs that it found internally at Meta. I hope these examples will convince you to try enabling the rule in the projects you work on!

no-constant-binary-expression 做什么?

🌐 What does no-constant-binary-expression do?

该规则检查那些比较(==!== 等)其结果在运行时不会变化的情况,以及逻辑表达式(&&??||),这些表达式要么总是短路,要么从不短路。

🌐 The rule checks for comparisons (==, !==, etc) where the outcome cannot vary at runtime, and logical expressions (&&, ??, ||) which will either always or never short-circuit.

例如:

🌐 For example:

  • +x == null 永远是 false,因为 + 会将 x 强制转换为数字,而数字永远不会是 nullish。
  • { ...foo } || DEFAULT 永远不会返回 DEFAULT,因为对象总是为真。

这两者都是看起来会影响程序计算方式的表达式的例子,但实际上并不会。

🌐 Both of these are examples of expressions that look like they can affect the way the program evaluates, but in reality, do not.

这个规则最初只是尝试检测不必要的空检查。然而,当我在研究它的时候,我意识到无用的空检查只是一个更广泛类别的特殊情况:无用代码。最终我明白了:开发者并不打算写无用代码,而与开发者意图不符的代码本质上就是一个错误。因此,你能够检测到的任何无用代码都是一个错误。

🌐 This rule originally started as just an attempt to detect unnecessary null checks. However, as I worked on it, I realized useless null checks were just a special case of a broader category: useless code. Eventually it clicked for me: developers don’t intend to write useless code, and code that does not match the developer’s intent is by definition a bug. Therefore, any useless code you can detect is a bug.

当我在 Meta 对我们的代码库运行规则的第一个版本时,这种认识得到了确认,它检测出了各种微妙而有趣的错误,这些错误在代码审查中未被发现。

🌐 This realization was confirmed for me when I ran the first version of the rule against our code base at Meta, and it detected a wide variety of subtle and interesting bugs which had made it through code review.

在真实世界中使用 no-constant-binary-expressions 发现的漏洞

🌐 Real world bugs found with no-constant-binary-expressions

在本节中,我将分享这一规则可以捕获的多种类型的漏洞。每种类型至少包括一个在受欢迎的开源项目中检测到的具体示例。我在这里选择包含真实示例,并不是为了羞辱任何人或任何项目,而是为了强调这些错误是任何团队都可能轻易犯的。

🌐 In this section I’ll share a number of types of bugs that this rule can catch. Each includes at least one concrete example detected in a popular open source project. My choice to include real examples here is not to shame anyone or any project, but to drive home the fact that these are errors that any team can easily make.

混淆的运算符优先级

🌐 Confusing operator precedence

该规则发现的最常见的错误类型是开发者误解运算符优先级的地方,特别是一元运算符,如 !+typeof

🌐 The most common class of bug the rule finds is places where developers misunderstood the precedence of operators, particularly unary operators like !, + and typeof.

if (!whitelist.has(specifier.imported.name) == null) {
  return;
}

来自 Material UI(也包括:VS Code 1, 2, Webpack, Mozilla

混淆 ??|| 的优先级

🌐 Confusing ?? and || precedence

在尝试定义默认值时,人们会对像 a === b ?? c 这样的表达式感到困惑,并假设它会被解析为 a === (b ?? c)。但实际上,它会被解析为 (a === b) ?? c

🌐 When trying to define default values, people get confused with expressions like a === b ?? c and assume it will be parsed as a === (b ?? c). When in actuality it will be parsed as (a === b) ?? c.

shouldShowWelcome() {
  return this.viewModel?.welcomeExperience === WelcomeExperience.ForWorkspace ?? true;
}

来自 VS Code

旁注:观察到开发者经常被运算符优先级弄糊涂,这促使我尝试使用 一个 VS Code 扩展 来直观地澄清优先级的解释方式。

期望对象按值比较

🌐 Expecting objects to be compared by value

来自其他使用按值而非按引用比较结构的语言的开发者,可能很容易陷入认为可以通过与新创建的空对象比较来测试一个对象是否为空的误区。当然,在 JavaScript 中,对象是按引用比较的,且没有任何值可以等于新构造的对象字面量。

🌐 Developers coming from other languages where structures are compared by value, rather than by reference, can easily fall into the trap of thinking they can do things like test if an object is empty by comparing with a newly created empty object. Or course in JavaScript, objects are compared by reference, and no value can ever be equal to a newly constructed object literal.

在这个例子中,hasData 将始终被设置为 true,因为 data 永远不可能与新创建的对象引用相等。

🌐 In this example, hasData will always be set to true because data can never be referentially equal to a newly created object.

hasData = hasData || data !== {};

来自 Firefox(也可写作:Firefox

期望空对象为 falsenull

🌐 Expecting empty objects to be false or null

另一种常见的 JavaScript 错误类型是期望空对象为 null 或假值。对于来自像 Python 这样的语言的人来说,这很容易出错,因为在 Python 中空列表和字典是假值。

🌐 Another common categrory of JavaScript error is expecting empty objects to be nullish or falsy. This is likely an easy mistake to make for folks coming from a language like Python where empty lists and dictionaries are falsy.

const newConfigValue = { ...configProfiles } ?? {};

来自 VS Code(也可写作:VS Code 1, 2

>= 还是 =>

🌐 Is it >= or =>?

我只见过这个特定的拼写错误一次,但我想把它包括进来,因为它是这个规则能捕捉到的意想不到类型的错误的一个很好的例子。

🌐 I’ve only seen this particular typo once, but I wanted to include it because it’s a great example of the unexpected types of bugs this rule can catch.

在这里,开发者本意是测试一个值是否大于或等于零(>= 0),但不小心把字符顺序颠倒了,创建了一个返回 0 && startWidth <= 1 的箭头函数!

🌐 Here, the developer meant to test if a value was greater than or equal to zero (>= 0), but accidentally reversed the order of the characters and created an arrow function that returned 0 && startWidth <= 1!

assert(startWidth => 0 && startWidth <= 1);

来自 Mozilla

no-constant-binary-expression 捕获的其他错误

🌐 Other errors caught by no-constant-binary-expression

上述五类错误并非详尽无遗。当我最初在 Meta 的(非常)大的单体仓库上运行这个规则的第一个版本时,它发现了超过 500 个问题。虽然许多问题属于上述列出的类别,但也有一些长尾的其他有趣的错误。一些亮点包括:

🌐 The above five categories of errors are not exhaustive. When I originally ran the first version of this rule on our (very) large monorepo at Meta, it found over 500 issues. While many fell into the categories outlined above, there was also a long-tail of other interesting bugs. Some highlights include:

  • 思考 || 允许进行集合操作:states.includes('VALID' || 'IN_PROGRESS')
  • 认为原始函数会通过空值:Number(x) == null
  • 不知道原始 构造函数 返回封装的原始类型:new Number(x) === 10

我本不会单独去针对这些特定问题进行代码检查,但仅仅通过尝试识别任何“无用”的东西,我们就能够发现并纠正它们。

🌐 I never would have set out to lint for these specific issues individually, but by simply trying to identify anything “useless” we were able to find and correct them.

结论

🌐 Conclusion

正如你现在所看到的,no-constant-binary-expression 能够检测多种不同类型的错误。这条规则之所以能做到这一点,并不是因为它被编程去寻找那些特定的问题,而是因为所有这些错误有一个共同点:它们表现为无用的代码。由于开发者通常不打算编写无用的代码,所以检测无用的代码通常会导致发现错误。

🌐 As you’ve now seen no-constant-binary-expression is capable of detecting a variety of different types of bugs. The rule accomplishes this not because it’s programmed to look for those specific issues, but because all those bugs have one thing in common: they manifest as useless code. Because developers generally don’t intend to write useless code, detecting useless code generally results in detecting bugs.

如果你觉得这些例子有说服力,请考虑在你的 ESLint 配置中启用 no-constant-binary-expression

🌐 If you’ve found these examples compelling, please consider enabling no-constant-binary-expression in your ESLint config:

// eslintrc
module.exports = {
  rules: {
    // Requires eslint >= v8.14.0
    "no-constant-binary-expression": "error"
  }
}

如果你这样做,并且它发现了漏洞,我很想听到相关信息

🌐 If you do, and it finds bugs, I’d love to hear about them!

感谢 Brad Zacher 提出的最初观察,这启发了这项工作,并建议将其作为新的核心规则提出。同时感谢 Milos Djermanovic 在代码评审期间的重要贡献。

最新的 ESLint 新闻、案例研究、教程和资源。

ESLint v10.3.0 发布
1 min read

ESLint v10.3.0 发布

我们刚刚发布了 ESLint v10.3.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。

ESLint v10.2.1 发布
1 min read

ESLint v10.2.1 发布

我们刚刚发布了 ESLint v10.2.1,这是 ESLint 的一个补丁版本升级。本次发布修复了上一版本中发现的几个错误。

ESLint v10.2.0 发布
2 min read

ESLint v10.2.0 发布

我们刚刚发布了 ESLint v10.2.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。