eslint-plugin-unicorn 规则实战:throw-new-error 如何强制使用 `new` 创建错误对象
2026/9/19 5:48:46 网站建设 项目流程

eslint-plugin-unicorn 规则实战:throw-new-error 如何强制使用new创建错误对象

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

本文聚焦 eslint-plugin-unicorn 的throw-new-error规则:它强制在创建错误对象时使用new关键字,涵盖规则背景、配置方式、源码检测逻辑、自动修复机制,以及基于 AVA 快照测试的 27 个无效用例全解,帮助你彻底理解该规则的判定边界与修复行为。

规则背景与动机

JavaScript 语言允许不借助new关键字直接调用Error()TypeError()等内置错误构造函数,其行为与new Error()几乎等价。但这种写法不够明确:调用普通函数与构造错误对象在语义上容易混淆,可读性较差。

eslint-plugin-unicorn 的throw-new-error规则正是为此设计:要求创建错误对象时必须显式使用new。规则的完整说明见 docs/rules/throw-new-error.md,其核心主张是:

While it's possible to create a new error without using thenewkeyword, it's better to be explicit.

该规则属于suggestion类型,并且可被--fix自动修复。在配置层面,它被同时纳入两套推荐配置(见规则文档头部的配置声明):

  • recommended:推荐配置
  • ☑️unopinionated:非观点性配置(该规则在recommended中实际标注为unopinionated,见 rules/throw-new-error.js)

规则效果速览

内置错误构造函数

// ❌ 错误写法 const error = Error('unicorn'); // ✅ 正确写法 const error = new Error('unicorn');
// ❌ 错误写法 throw TypeError('unicorn'); // ✅ 正确写法 throw new TypeError('unicorn');

成员表达式中的错误构造

// ❌ 错误写法 throw lib.TypeError('unicorn'); // ✅ 正确写法 throw new lib.TypeError('unicorn');

源码级检测逻辑解析

规则实现位于 rules/throw-new-error.js,整体流程围绕CallExpression节点展开。核心逻辑可分四步:

1. 名称匹配规则

规则定义了一个自定义错误命名正则:

const customError = /^(?:[A-Z][\da-z]*)*Error$/;

该正则要求 callee 名称必须以Error结尾,且前面的每一段都要满足「大写字母开头 + 数字或小写字母」的模式。这意味着它不仅匹配内置错误(ErrorTypeErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorURIError),也匹配用户自定义错误类(如CustomErrorFooBarBazErrorABCErrorAbc3Error)。

2. 匹配的节点形态

规则只检查两种 callee 形态(见 rules/throw-new-error.js):

  • IdentifierError()TypeError()CustomError()这类直接调用;
  • MemberExpression(非计算属性、property 为Identifier):lib.Error()lib.mod.Error()lib[mod].Error()这类通过对象属性访问的错误构造。
// 关键判断条件 (callee.type === 'Identifier' && customError.test(callee.name)) || ( callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier' && customError.test(callee.property.name) )

3. 明确排除的场景

以下情况规则不会报错(对应测试文件 test/throw-new-error.js 中的valid用例):

排除场景示例原因
已使用newthrow new Error()本就不存在违规
名称不匹配FooError风格throw getError()throw lib.getError()不是错误构造函数命名
CallExpressionthrow CustomError只是引用,没有调用
callee 不是 Identifier/MemberExpressionthrow getErrorConstructor()()返回值不确定,无法判断
计算属性成员表达式throw lib[Error]()computed为 true,属性名是变量
属性不是 Identifierthrow lib["Error"]()字符串属性名不匹配Identifier判断
可选链调用throw Error?.()throw lib?.Error()throw lib?.foo.Error()new无法作用于可选链调用
可选链位于 callee 内部throw lib?.foo.Error().message即使调用不在链最外层也排除
Effect 库的Data.TaggedErrorclass QueryError extends Data.TaggedError('QueryError') {}这是 Effect 库的特殊 API,见 issue #2654

规则对可选链的排除逻辑很关键:new关键字无法作用于可选链调用(如Error?.()不能写成new Error?.()),因此当调用本身是可选链(node.optional)或 callee 中包含可选链元素(hasOptionalChainElement(callee))时直接返回(见 rules/throw-new-error.js)。

4. 装饰器调用的特殊处理

规则对直接作为装饰器调用的表达式做了豁免(node.parent.type === 'Decorator'时返回),避免误伤 TypeScript 装饰器语法,例如:

@RegisterServiceError() export class SomeError extends Error {}

但注意:装饰器的参数仍会被检查。测试用例@Decorator(Error()) export class SomeError extends Error {}就是无效代码,因为Error()作为装饰器参数被调用(见 test/throw-new-error.js)。

自动修复机制:从Error()new Error()

规则报错时的消息为Use `new` when creating an error.messageIdthrow-new-error),同时提供自动修复。修复逻辑委托给switchCallExpressionToNewExpression(见 rules/fix/switch-call-expression-to-new-expression.js),该函数也同时被new-for-builtins规则复用,实现在 rules/fix/index.js。

修复器由三部分 yield 组成:

1. 关键字周围空格修复

fixSpaceAroundKeyword(见 rules/fix/fix-space-around-keywords.js)会在调用表达式紧邻awaitof等关键字时补上空格,避免修复后产生awaitnew Error()之类的语法错误。它识别两类有问题的 token:

  • Keyword类型且值全是小写字母;
  • Identifier且值为of(对应for...of)或await

2. 插入new前缀

yield fixer.insertTextBefore(node, 'new ');

3. 为复杂 callee 添加括号

修复函数会检查 callee 是否包含调用表达式(如getGlobalThis().Errorutils.getGlobalThis().Error)。如果成员表达式内部含有CallExpression,则必须加括号包裹,否则会改变求值顺序:

// 修复前 throw getGlobalThis().Error() // 修复后 throw new (getGlobalThis().Error)()

判断逻辑实现在 rules/utils/should-add-parentheses-to-new-expression-callee.js:递归下钻MemberExpression.object直到非成员表达式节点,若最终是CallExpression则需要加括号。同时,如果 callee 本身已被括号包裹(isParenthesized),则不再重复添加。

快照测试全景:27 个无效用例深度解读

规则的测试采用 AVA 的snapshot 模式test.snapshot),快照文件为 test/snapshots/throw-new-error.js.md,对应源测试文件 test/throw-new-error.js。每个快照记录「输入 → 报错位置与消息 → 修复输出」三部分。下面按场景归类解读全部 27 个无效用例。

场景一:内置错误构造函数直接调用(用例 1、12-17)

// 输入 throw Error() // → throw new Error() throw TypeError() // → throw new TypeError() throw EvalError() // → throw new EvalError() throw RangeError() // → throw new RangeError() throw ReferenceError() // → throw new ReferenceError() throw SyntaxError() // → throw new SyntaxError() throw URIError() // → throw new URIError()

这类最简单:Error及全部内置错误子类(TypeErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorURIError)都匹配customError正则。修复后都加上new前缀。

场景二:带参数的调用(用例 7-11)

throw Error('foo') // → throw new Error('foo') throw CustomError('foo') // → throw new CustomError('foo') throw FooBarBazError('foo') // → throw new FooBarBazError('foo') throw ABCError('foo') // → throw new ABCError('foo') throw Abc3Error('foo') // → throw new Abc3Error('foo')

注意Abc3Error也被识别——正则(?:[A-Z][\da-z]*)*中的\d允许段内出现数字(Abc3满足「大写开头 + 数字小写混合」),这验证了命名规则对含数字错误类名的覆盖。

场景三:成员表达式(用例 3-6)

throw lib.Error() // → throw new lib.Error() throw lib.mod.Error() // → throw new lib.mod.Error() throw lib[mod].Error() // → throw new lib[mod].Error() throw (lib.mod).Error() // → throw new (lib.mod).Error()

这里展示了成员表达式修复的两个细节:

  • lib[mod].Error()lib[mod]计算属性,但规则的检查只看callee.property(即.Error这一层),因此仍然命中;
  • (lib.mod).Error()修复时new插入在括号外层:new (lib.mod).Error()

场景四:冗余括号(用例 2、18-19)

throw (Error)() // → throw new (Error)() throw (( URIError() )) // → throw (( new URIError() )) throw (( URIError ))() // → throw new (( URIError ))()

三者的修复位置不同:(Error)()new加在括号外;(( URIError() ))Error()本身是 callee,new插入在最内层Error前;(( URIError ))()中调用发生在括号外,new加在整体前。快照的报错高亮区间(^^^^^^^)也精确指向实际调用位置。

场景五:全局对象链上的错误(用例 20-22)

throw getGlobalThis().Error() // → throw new (getGlobalThis().Error)() throw utils.getGlobalThis().Error() // → throw new (utils.getGlobalThis().Error)() throw (( getGlobalThis().Error ))() // → throw new (( getGlobalThis().Error ))()

这组用例直接验证了「callee 含调用表达式必须加括号」的修复逻辑:getGlobalThis().Error是成员表达式且其objectCallExpression,因此必须包裹成(getGlobalThis().Error)再前置new,否则new getGlobalThis().Error()的语义会出错。

场景六:非 throw 上下文(用例 23-27)

规则并不局限于throw语句,任何创建错误对象的CallExpression都会被检查:

// 变量声明 const error = Error() // → const error = new Error() // 函数参数 throw Object.assign(Error(), {foo}) // → throw Object.assign(new Error(), {foo}) // Promise reject 回调 new Promise((resolve, reject) => { reject(Error('message')); // → reject(new Error('message')); }); // 数组索引访问 function foo() { return[globalThis][0].Error('message'); // → return new [globalThis][0].Error('message'); } // 装饰器参数(TypeScript + decorators 解析器) @Decorator(Error()) // → @Decorator(new Error()) export class SomeError extends Error {}

其中return[globalThis][0].Error('message')展示了复杂计算成员表达式的修复结果new [globalThis][0].Error('message')——由于 callee 的最内层 object 是ArrayExpression而非CallExpression,不需要加括号。

@Decorator(Error())用例使用了 TypeScript 解析器与decorators: true的 parserOptions(见 test/throw-new-error.js),证明规则对装饰器参数中的错误构造依然生效,同时验证了修复不会破坏装饰器语法。

快照工作流说明

快照文件由 AVA 测试框架自动生成与校验(文件头标注 "Generated by AVA")。当规则行为或消息文案变化时,需要更新快照(通常通过npm test -- --update-snapshots或 AVA 的-u参数)。快照的「Input / Error Message / Output」三段式结构,为回归测试提供了逐字节的精确比对,任何修复输出的细微变化都会在 CI 中被捕获。

规则注册与整体集成

该规则通过 rules/index.js 注册为throw-new-error

export {default as 'throw-new-error'} from './throw-new-error.js';

在实际项目中启用该规则:

// eslint.config.js(flat config 写法) import unicorn from 'eslint-plugin-unicorn'; export default [ { plugins: {unicorn}, rules: { 'unicorn/throw-new-error': 'error', }, }, ];

启用后运行npx eslint . --fix即可自动修复所有未使用new的错误构造。

总结

throw-new-error是 eslint-plugin-unicorn 中一个「小而精」的规则:核心逻辑仅几十行,却通过精准的命名正则、可选链豁免、装饰器特判和 Effect 库兼容处理,覆盖了错误构造的绝大多数真实场景。其自动修复器还复用了new-for-builtins的公共修复基础设施,对awaitof相邻关键字和含调用表达式的 callee 做了语法安全处理。27 个快照用例完整记录了每种边界情况的报错位置与修复输出,是理解该规则行为最权威的参考,也展示了项目「文档 → 实现 → 测试快照」三位一体的工程质量。

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询