eslint-plugin-unicornno-instanceof-builtins规则深度解析:快照测试、自动修复与安全类型检查实战指南
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
本篇技术指南以 eslint-plugin-unicorn 中no-instanceof-builtins规则为核心,结合其源码实现(rules/no-instanceof-builtins.js)、官方文档(docs/rules/no-instanceof-builtins.md)、单元测试(test/no-instanceof-builtins.js)与 AVA 快照(test/snapshots/no-instanceof-builtins.js.md),完整讲解该规则的报错信息、自动修复行为、strategy/include/exclude/useErrorIsError四个配置项,以及它如何安全处理 Vue 模板、globalThis成员访问与低优先级左操作数等边界场景。读完本文,你将掌握该规则从配置、报错到自动修复的完整工作链路,并能在自己的 ESLint 项目中正确启用和调优它。
规则概述:为什么要禁止对内置对象使用instanceof
no-instanceof-builtins是一条"问题型"(type: 'problem')规则,其目的是禁止对内置对象使用instanceof进行类型检查。规则在源码中声明的描述为 "Disallowinstanceofwith built-in objects",对应报错信息为:
Avoid using
instanceoffor type checking as it can lead to unreliable results.
其根本原因在于instanceof依赖原型链判断,存在跨 realm(多个全局环境,如 iframe、Worker 等)失效的问题——来自不同 realm 的同一个内置构造器并不共享原型链,导致foo instanceof Array可能对"真正的数组"返回false。因此规则文档建议改用更安全的方式,例如Object.prototype.toString.call(foo),或者借助@sindresorhus/is这类库进行类型判断(见 docs/rules/no-instanceof-builtins.md)。
规则元信息速览
从 rules/no-instanceof-builtins.js 的meta配置可以确认以下关键信息:
| 项目 | 值 | 说明 |
|---|---|---|
type | problem | 属于潜在缺陷类规则 |
fixable | code | 支持--fix自动修复 |
hasSuggestions | true | 同时提供编辑器可手动应用的建议修复 |
recommended | unopinionated | 属于非强制推荐配置 |
languages | ['js/js'] | 适用于 JS 文件,且经 Vue 模板检查包装后同样作用于.vue模板表达式 |
在规则配置层面,recommended与unopinionated两个预置 config 都会启用该规则;其默认选项为useErrorIsError: false、strategy: 'loose'、include: []、exclude: [](见 rules/no-instanceof-builtins.js)。
规则默认行为:loose 策略下的报错与自动修复
规则的默认匹配策略是loose,此时只针对"原始类型包装构造器 +Function+Array"共七类目标报错。从 test/no-instanceof-builtins.js 的looseStrategyInvalid用例可见其完整清单:
String、Number、Boolean、BigInt、Symbol(对应源码 rules/no-instanceof-builtins.js 中的primitiveWrappers集合)Function、Array
在 test/snapshots/no-instanceof-builtins.js.md 中,这些用例的快照结果清晰展示了自动修复的差异:
原始类型包装构造器 →typeof建议
对于foo instanceof String,规则报告错误并给出"建议修复"(Suggestion,不会在--fix时直接应用):
// 输入(报错) foo instanceof String; // 建议修复 typeof foo === 'string';Number、Boolean、BigInt、Symbol依次对应typeof foo === 'number' | 'boolean' | 'bigint' | 'symbol',其中类型名由构造器名小写化得到(见源码replaceWithTypeOfExpression中的constructorName.toLowerCase(),rules/no-instanceof-builtins.js)。建议的文案固定为 "Switch totypeof … === '{{type}}'."(rules/no-instanceof-builtins.js)。
Array→Array.isArray()自动修复
foo instanceof Array会被直接自动修复为Array.isArray(foo)(Output 而非 Suggestion,见快照 test/snapshots/no-instanceof-builtins.js.md),因为该替换是无损且安全的。类似的直接修复还覆盖了大量形态:[] instanceof Array→Array.isArray([])、[1,2,3] instanceof Array === true→Array.isArray([1,2,3]) === true、fun.call(1, 2, 3) instanceof Array、obj.arr instanceof Array、foo.bar[2] instanceof Array、(0, array) instanceof Array、function foo(){return[]instanceof Array}→function foo(){return Array.isArray([])}(见 test/snapshots/no-instanceof-builtins.js.md),可见修复逻辑对成员表达式、调用表达式、逗号表达式等左操作数都能稳定包裹。
Function→typeof自动修复
foo instanceof Function会自动修复为typeof foo === 'function'(快照 test/snapshots/no-instanceof-builtins.js.md)。
低优先级左操作数的括号处理:a + b instanceof Function
快照中存在一组专门的用例(对应测试 test/no-instanceof-builtins.js),用于验证"低优先级左操作数必须加括号,使typeof作用于整个表达式":
a + b instanceof Function→typeof (a + b) === 'function'a + b instanceof String→typeof (a + b) === 'string'a - b instanceof Function→typeof (a - b) === 'function'(a + b) instanceof Function→typeof (a + b) === 'function'(原已加括号则保持)
对应快照见 test/snapshots/no-instanceof-builtins.js.md。这一行为由源码中的括号判断实现:只有当左操作数未加括号、且满足shouldAddParenthesesToUnaryExpressionArgument(left, 'typeof')时才补上typeof (...),否则直接前缀typeof(rules/no-instanceof-builtins.js)。该工具函数基于运算符优先级判断(getPrecedence(node) < PRECEDENCE_UNARY时需加括号),避免生成typeof a + b === 'function'这类语义错误的代码(见 rules/utils/should-add-parentheses-to-unary-expression.js)。
strict 策略:覆盖全部内置构造器
当配置strategy: 'strict'时,规则会额外匹配一组内置构造器清单(源码 rules/no-instanceof-builtins.js 的strictStrategyConstructors):
| 分类 | 构造器 |
|---|---|
| 错误类型 | Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError、AggregateError、SuppressedError(来自 rules/shared/builtin-errors.js) |
| 集合类型 | Map、Set、WeakMap、WeakRef、WeakSet |
| 数组与类型化数组 | ArrayBuffer、Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float16Array、Float32Array、Float64Array、BigInt64Array、BigUint64Array(来自 rules/shared/typed-array.js) |
| 数据类型 | Object |
| 正则 | RegExp |
| 异步与函数 | Promise、Proxy |
| 其他 | DataView、Date、SharedArrayBuffer、FinalizationRegistry |
该清单与测试文件中的strictStrategyInvalid用例一一对应(test/no-instanceof-builtins.js)。在 strict 策略下,这些构造器(如fooStrict instanceof Map、fooStrict instanceof Int8Array、fooStrict instanceof Date等)会报出同样的错误信息,但不提供自动修复——快照中均只有 Message、没有 Output 或 Suggestion(例如 test/snapshots/no-instanceof-builtins.js.md 的Map用例)。原因很直观:对这些复杂内置对象并没有单一可靠的替代写法,规则只负责提醒开发者改用安全方案。
需要注意:strict 策略是在 loose 七类目标之上叠加匹配的。测试中 strict 策略的用例集合为[...looseStrategyInvalid, ...strictStrategyInvalid](test/no-instanceof-builtins.js),因此fooStrict instanceof String在 strict 下同样报错并给出typeof建议。
配置示例
'unicorn/no-instanceof-builtins': [ 'error', { strategy: 'strict', }, ]include / exclude:自定义匹配名单
除内置清单外,规则支持通过include与exclude两个数组选项定制匹配范围,其配置 schema 与默认值见 rules/no-instanceof-builtins.js。
include:追加需要校验的构造器
默认[],用于把内置清单之外的构造器加入检查。文档示例(docs/rules/no-instanceof-builtins.md):
'unicorn/no-instanceof-builtins': [ 'error', { include: [ 'WebWorker', 'HTMLElement', ], }, ]测试与快照验证了该行为:fooInclude instanceof WebWorker与fooInclude instanceof HTMLElement在配置include: ['WebWorker']、include: ['HTMLElement']后均会报错(test/no-instanceof-builtins.js、test/snapshots/no-instanceof-builtins.js.md)。与之对照,默认 loose 策略下foo instanceof WebWorker是合法用例(test/no-instanceof-builtins.js)。
从实现上看,include在两种策略下的作用不同(rules/no-instanceof-builtins.js):
const forbiddenConstructors = new Set(strategy === 'strict' ? [...strictStrategyConstructors, ...include] : include);- strict 策略:
include追加到内置清单之后; - loose 策略:
include直接构成唯一的匹配来源(此时规则匹配范围 = 七类默认目标 + include 列表)。
exclude:优先排除指定构造器
默认[],用于从所有匹配中排除指定构造器,且优先级高于其他所有配置(包括默认目标和include)。文档示例:
'unicorn/no-instanceof-builtins': [ 'error', { exclude: [ 'String', 'Number', ], }, ]测试验证了排除优先级:fooExclude instanceof Function/Array/String在配置exclude: ['Function']、['Array']、['String']后全部变为合法(test/no-instanceof-builtins.js)。实现上,规则在拿到构造器后第一步就检查exclude.includes(constructor.name),命中则直接返回(rules/no-instanceof-builtins.js)。
useErrorIsError:用Error.isError()判定错误对象
useErrorIsError(默认false)用于决定当右侧是Error时,是否将其修复为提案中的Error.isError()调用。开启后,fooErr instanceof Error会被自动修复为Error.isError(fooErr)(快照 test/snapshots/no-instanceof-builtins.js.md):
'unicorn/no-instanceof-builtins': [ 'error', { strategy: 'strict', useErrorIsError: true, }, ]- 开启且匹配
Error:走"函数调用修复"分支,替换为Error.isError(...)(源码 rules/no-instanceof-builtins.js); - 开启但匹配其他错误类型(如
err instanceof EvalError、RangeError等):仍然报错,但不产生修复(见 test/snapshots/no-instanceof-builtins.js.md); - 关闭:
foo instanceof Error在 loose 策略下不会报错(不属于七类默认目标),在 strict 策略下报错但不提供修复。
需要特别说明的是,该选项文档已注明"会在未来的某个版本移除"(docs/rules/no-instanceof-builtins.md),因为Error.isError()仍是提案阶段 API(TC39 proposal-is-error),使用时需要关注其落地进度与运行环境兼容性。
全局对象成员访问的识别与修复
规则不仅匹配裸标识符,还能识别通过globalThis/window/self/global访问的内置构造器,其判定条件是:非计算属性、属性为标识符、对象名属于四个全局对象名之一、且该对象确实指向全局对象(isGlobalIdentifier校验,见 rules/no-instanceof-builtins.js)。
快照中对应的典型修复(test/snapshots/no-instanceof-builtins.js.md):
foo instanceof globalThis.String→ 建议typeof foo === 'string'foo instanceof globalThis.Function→ 自动修复typeof foo === 'function'foo instanceof globalThis.Array→ 自动修复globalThis.Array.isArray(foo)foo instanceof window.Array→window.Array.isArray(foo)foo instanceof self.Array→self.Array.isArray(foo)foo instanceof global.Array→global.Array.isArray(foo)
注意修复时会保留引用前缀(referenceText),例如全局对象写法下Array分支生成${referenceText}.isArray(...),因此输出是globalThis.Array.isArray(foo)而非裸Array.isArray(foo)(rules/no-instanceof-builtins.js)。这能避免在const Array = {isArray: () => false};这类局部遮蔽场景下产生行为变化——测试专门覆盖了该用例,修复后仍指向globalThis.Array(快照 test/snapshots/no-instanceof-builtins.js.md)。
同时,规则对"伪全局访问"保持克制:当局部变量遮蔽了全局对象名(如const globalThis = {Array}; foo instanceof globalThis.Array、const window = {Array}; foo instanceof window.Array)或使用计算属性(foo instanceof globalThis["Array"])时,均判定为合法(test/no-instanceof-builtins.js)。
Vue 模板与<script>中的检查与修复
规则通过checkVueTemplate(create)包装(rules/no-instanceof-builtins.js),使得监听器同时作用于.vue文件的模板表达式与<script>块。该包装器在检测到vue-eslint-parser提供的defineTemplateBodyVisitor时,将同一组监听器同时注册到模板 body 与 script 上(见 rules/utils/rule.js)。
快照中基于 vue 解析器的用例验证了模板与 script 内的行为(test/snapshots/no-instanceof-builtins.js.md):
<!-- 模板表达式:自动修复 --> <template><div v-if="array instanceof Array" v-for="element of array"></div></template> <!-- 修复为 --> <template><div v-if="Array.isArray(array)" v-for="element of array"></div></template> <!-- 带括号嵌套的模板表达式 --> <template><div v-if="(( (( array )) instanceof (( Array )) ))"></div></template> <!-- 修复为 --> <template><div v-if="(( Array.isArray((( array ))) ))"></div></template> <!-- 插值表达式 --> <template><div>{{(( (( array )) instanceof (( Array )) )) ? array.join(" | ") : array}}</div></template> <!-- 修复为 --> <template><div>{{(( Array.isArray((( array ))) )) ? array.join(" | ") : array}}</div></template> <!-- script 块 --> <script>const foo = array instanceof Array</script> <!-- 修复为 --> <script>const foo = Array.isArray(array)</script> <script>foo instanceof Function</script> <!-- 修复为 --> <script>typeof foo === 'function'</script>值得注意的是模板环境下的引号适配:生成typeof比较时,规则会检测是否处于 Vue 表达式容器中,若模板表达式外层使用双引号,则改用单引号生成字符串字面量(如v-if="typeof foo === 'string'"),避免引号冲突(源码 rules/no-instanceof-builtins.js)。
修复实现的底层细节
从源码看,规则的自动修复由两个 generator 函数实现,均基于 token 级别的精确替换:
replaceWithFunctionCall(rules/no-instanceof-builtins.js):用于Array/Error.isError分支。先调用fixSpaceAroundKeyword规整instanceof两侧空白,然后在左操作数外层插入函数名(与),最后删除instanceoftoken 与右侧构造器 token。replaceWithTypeOfExpression(rules/no-instanceof-builtins.js):用于typeof分支。按需给低优先级左操作数加括号,把instanceoftoken 替换为===,再把右侧构造器替换为对应的小写类型字符串。
两者都使用getParenthesizedRange获取包含外层括号的完整范围(例如(fooErr) instanceof (Error)修复后为Error.isError((fooErr)),快照 test/snapshots/no-instanceof-builtins.js.md),从而保证多级括号、注释等细节在修复过程中被完整保留。快照中还有一个覆盖了大量嵌套括号与注释的极端用例,修复输出逐行保留了所有注释与缩进(test/snapshots/no-instanceof-builtins.js.md),直观体现了该修复链的健壮性。
另一个细节:当右侧构造器内部含注释时(如foo instanceof Function /* keep */),Function与包装类型的自动修复/建议会被抑制(源码 rules/no-instanceof-builtins.js 中的hasCommentsInside判断),避免删除用户注释。
快速上手与验证
在项目中启用规则
安装并启用eslint-plugin-unicorn后,在 ESLint 配置中加入:
'unicorn/no-instanceof-builtins': [ 'error', { strategy: 'strict', useErrorIsError: false, include: [], exclude: [], }, ]随后运行npx eslint --fix .即可对Array、Function、Error(开启useErrorIsError时)等场景执行自动修复;对String、Number等包装类型会输出"建议修复",可在编辑器中手动应用。
通过仓库测试与快照深入理解
- 规则源码:rules/no-instanceof-builtins.js
- 官方文档:docs/rules/no-instanceof-builtins.md
- 单元测试:test/no-instanceof-builtins.js(约 40 个断言组,覆盖 loose / strict / include / exclude / useErrorIsError / 全局对象 / Vue 模板 / 括号与注释等场景)
- 完整快照:test/snapshots/no-instanceof-builtins.js.md(共 2020 行,逐条记录了每个无效用例的输入、报错位置、消息与修复输出,是最直观的行为文档)
- 配套共享数据:rules/shared/builtin-errors.js、rules/shared/typed-array.js
运行规则相关测试可使用npx ava test/no-instanceof-builtins.js,快照文件即由 AVA 自动生成与维护(test/snapshots/no-instanceof-builtins.js.md头部注明由 AVA 生成,实际快照数据保存在no-instanceof-builtins.js.snap中)。
总结
no-instanceof-builtins是 eslint-plugin-unicorn 中一条"防患于未然"的问题型规则:它以instanceof跨 realm 不可靠为核心动机,用 loose / strict 两档策略覆盖从原始类型包装构造器到全部内置构造器的检查范围,并通过 include / exclude / useErrorIsError 三个选项提供灵活的定制空间。它的自动修复对Array、Function与Error.isError场景能做到语义无损的 token 级替换,对低优先级操作数、全局对象成员访问、Vue 模板与复杂括号注释环境均有完善的边界处理。快照文件(test/snapshots/no-instanceof-builtins.js.md)逐条固化了这些行为,既是回归测试的守护,也是理解该规则全部细节的最佳阅读材料。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考