ESLint 规则详解:space-unary-ops 一元运算符空格一致性检查
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
本篇技术指南以 ESLint 核心规则space-unary-ops(规则文档)为主体,结合 规则实现源码 与 完整测试套件 展开。读完本文,你将掌握该规则对words(如new、typeof、delete)与nonwords(如++、!、-)两类一元运算符的空格校验逻辑、words/nonwords/overrides三组配置项的语义与默认值,以及该规则在自动修复(--fix)时的边界行为与废弃迁移注意事项。
规则定位与核心目的
space-unary-ops是一条layout(排版布局)类规则(见 conf/rule-type-list.json),它要求代码风格规范中在一元运算符之前或之后保留空格。这主要是风格问题,但某些一元运算符在不加空格书写时会产生难以阅读和维护的表达式,例如:
typeof!foo; // 可读性差,容易与 typeof(foo) 混淆 void{foo:0}; // 运算符与操作数粘连从 规则源码 的meta可以看到,该规则:
- 类型为
layout,不属于recommended配置(recommended: false),即默认推荐配置不会启用它,需要显式开启; - 支持自动修复:
fixable: "whitespace",可通过--fix或编辑器内联修复自动插入/删除空格; - 自带完整的 JSON Schema 校验(
words、nonwords、overrides三个属性,且不允许额外属性additionalProperties: false)。
一元运算符的两大分类
该规则将一元运算符划分为words(单词型)与nonwords(符号型)两类,并分别管理它们与操作数之间的空格:
words 一元运算符
指由关键字构成的运算符:
// new var joe = new Person(); // delete var obj = { foo: 'bar' }; delete obj.foo; // typeof typeof {} // object // void void 0 // undefinedwords类运算符还包括yield与await。从实现看,规则分别通过 checkForSpacesAfterYield 和 checkForSpacesAfterAwait 处理YieldExpression与AwaitExpression节点。
nonwords 一元运算符
指由符号构成的运算符:
if ([1,2,3].indexOf(1) !== -1) {}; foo = --foo; bar = bar++; baz = !foo; qux = !!baz;nonwords覆盖-、+、--、++、!、!!等。
语法必需空格与可选空格的区别
一个容易被忽略的关键细节是:对于words运算符,规则只在空格不是语法强制要求时才生效。
delete obj.foo中的空格是语法必需的(必须写成delete obj.foo形式),因此该规则不会对它做任何检查;- 而
delete(obj.foo)中的空格是可选的——delete(obj.foo)与delete (obj.foo)语法上都合法,规则才会介入。
测试用例印证了这一点(tests/lib/rules/space-unary-ops.js):delete foo.bar、delete foo["bar"]在{ words: true }下均为有效代码,而delete(foo.bar)在{ words: true }下会被报告并修复为delete (foo.bar)。
配置选项详解
规则接受一个对象作为唯一选项,共三个字段(默认值与 Schema 定义见 lib/rules/space-unary-ops.js):
| 选项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
words | boolean | true | 是否要求new、delete、typeof、void、yield、await等单词型一元运算符后面必须有空格 |
nonwords | boolean | false | 是否要求-、+、--、++、!、!!等符号型一元运算符前后必须有空格 |
overrides | object | {} | 按运算符逐个覆盖上述两项设置,值为boolean,为空时不影响words/nonwords |
使用示例
"space-unary-ops": [ 2, { "words": true, "nonwords": false, "overrides": { "new": false, "++": true } }]以上配置的效果是:
- 全局要求
words运算符后跟空格、nonwords运算符前后不加空格; - 但通过
overrides例外地禁止new后出现空格(如new(Foo)),并要求++前后必须有空格(如++ foo、foo ++)。
从源码看,overrides的优先级最高:checkUnaryWordOperatorForSpaces 与 checkForSpaces 都先判断overrideExistsForOperator(基于Object.hasOwn判断),再回落到words/nonwords的全局设置。
默认配置下的错误与正确示例
默认配置{"words": true, "nonwords": false}的错误代码
/*eslint space-unary-ops: "error"*/ typeof!foo; void{foo:0}; new[foo][0]; delete(foo.bar); ++ foo; foo --; - foo; + "3";生成器中的yield:
/*eslint space-unary-ops: "error"*/ function *foo() { yield(0) }异步函数中的await:
/*eslint space-unary-ops: "error"*/ async function foo() { await(bar); }默认配置{"words": true, "nonwords": false}的正确代码
/*eslint space-unary-ops: "error"*/ // Word unary operator "typeof" is followed by a whitespace. typeof !foo; // Word unary operator "void" is followed by a whitespace. void {foo:0}; // Word unary operator "new" is followed by a whitespace. new [foo][0]; // Word unary operator "delete" is followed by a whitespace. delete (foo.bar); // Unary operator "++" is not followed by whitespace. ++foo; // Unary operator "--" is not preceded by whitespace. foo--; // Unary operator "-" is not followed by whitespace. -foo; // Unary operator "+" is not followed by whitespace. +"3";/*eslint space-unary-ops: "error"*/ function *foo() { yield (0) }/*eslint space-unary-ops: "error"*/ async function foo() { await (bar); }源码实现原理
监听五种 AST 节点
规则在 create 中监听五类节点:
return { UnaryExpression: checkForSpaces, UpdateExpression: checkForSpaces, NewExpression: checkForSpaces, YieldExpression: checkForSpacesAfterYield, AwaitExpression: checkForSpacesAfterAwait, };UnaryExpression:-x、+x、!x、typeof x、void x、delete x等;UpdateExpression:x++、++x、x--、--x;NewExpression:new Foo;YieldExpression、AwaitExpression:yield x、await x。
基于 token 区间(range)的判定
检查本质是比较相邻两个 token 的range是否相邻。例如 verifyWordHasSpaces 中,当secondToken.range[0] === firstToken.range[1](两个 token 之间没有任何字符)时,报告wordOperator消息,并调用fixer.insertTextAfter(firstToken, " ")插入空格;反之,verifyWordDoesntHaveSpaces 在secondToken.range[0] > firstToken.range[1](存在空格)时报告unexpectedAfterWord,并通过fixer.removeRange移除空白。
对于前后缀位置,checkForSpaces 区分了:
- 后缀形式(
node.prefix为 false,如foo++):取sourceCode.getLastTokens(node, 2)的最后两个 token,检查运算符前面是否该有空格; - 前缀形式:取
sourceCode.getFirstTokens(node, 2)的前两个 token,检查运算符后面是否该有空格。
消息文案(messageId)
源码中共定义六种消息(lib/rules/space-unary-ops.js):
| messageId | 语义 |
|---|---|
unexpectedBefore | 一元运算符前出现多余空格 |
unexpectedAfter | 一元运算符后出现多余空格 |
unexpectedAfterWord | 单词型一元运算符后出现多余空格 |
wordOperator | 单词型一元运算符后必须跟空格 |
operator | 符号型一元运算符后必须跟空格 |
beforeUnaryExpressions | 一元表达式前必须加空格(后缀++/--场景) |
两个值得注意的特例
1.!!双感叹号表达式
isFirstBangInBangBangExpression 用于识别!!expr布尔强转模式。当nonwords要求空格时,规则会跳过外层第一个!,只要求内层!与操作数之间满足空格要求(默认nonwords: false下则都要求不加空格)。测试中的!! foo→!!foo、!!foo→!! foo两个方向的修复都验证了这一点。
2.yield*委托表达式
checkForSpacesAfterYield 中,若node.delegate为真(即yield* iterable形式)或没有参数,直接返回不检查,避免与yield*委托语法的固有空格冲突。测试中yield* 0、yield * 0、yield*0在默认配置下均为合法代码。
自动修复的边界:token 合并保护
删除空格并不总是安全的——两个 token 紧贴后可能被解析成另一个新 token(如+ +foo去掉空格变成++foo)。为此,规则依赖 lib/rules/utils/ast-utils.js 中的canTokensBeAdjacent:
- 若左右 token 都是
Punctuator,且属于+/++或-/--同族组合,则返回false,禁止删除空格; - 若左侧是
/且右侧是注释或正则字面量,同样返回false。
在 verifyNonWordsDontHaveSpaces 的修复函数中,删除空格前先调用canTokensBeAdjacent,不能安全相邻时返回null(放弃自动修复)。测试套件中有对应的典型用例:+ +foo、+ ++foo、- -foo、- --foo在{ nonwords: false }下会报告错误但output: null(无法自动修复),而+ -foo可以安全修复为+-foo。
通过测试套件验证行为
完整测试文件 共 903 行,覆盖了words、nonwords、overrides的全部组合:
- words 系列:
delete(foo.bar)→delete (foo.bar)、new(Foo)→new (Foo)、typeof(foo)→typeof (foo)、void(0)→void (0)、yield(0)→yield (0)、await{...}→await {...}; - nonwords 系列:
! foo→!foo、foo ++→foo++、++ foo→++foo; - overrides 覆盖:
{ words: false, overrides: { new: true } }下new(Foo)仍被修复为new (Foo),说明overrides优先于全局选项; - async/await 与生成器:通过
languageOptions: { ecmaVersion: 6 }和ecmaVersion: 8分别启用yield与await语法支持,规则按 Flat Config 的 languageOptions 机制感知语法版本; - 私有字段场景:
yield#x in bar在{ words: false }下会被要求移除空格,覆盖了较新的 ECMAScript 语法组合。
项目内使用方式(Flat Config)
在 ESLint 的扁平配置(Flat Config)体系中,规则通过字符串名称注册与启用(注册入口见 lib/rules/index.js)。在eslint.config.js中启用并配置该规则的写法如下:
export default [ { rules: { "space-unary-ops": ["error", { words: true, nonwords: false, overrides: { new: false, "++": true } }] } } ];结合--fix命令行参数,或编辑器基于fixable: "whitespace"元数据提供的内联修复,即可自动统一一元运算符周围空格。
废弃状态与迁移建议
需要特别说明的是,该规则在ESLint v8.53.0 起被标记为废弃。从 lib/rules/space-unary-ops.js 的meta.deprecated可以看到:
- 废弃原因是格式化类规则正在移出 ESLint 核心;
deprecatedSince: "8.53.0",availableUntil: "11.0.0"(即最迟在 v11.0.0 移除);- 官方替代方案是ESLint Stylistic项目中维护的
space-unary-ops规则(插件名为@stylistic/eslint-plugin)。
因此,新项目或正在升级 ESLint 的项目,建议直接迁移到@stylistic/eslint-plugin的同名规则;已升级到较新 ESLint 版本的旧配置,也会在启用该规则时收到废弃提示。对于仍停留在早期版本的代码库,本文所述的配置与行为依然完全适用。
总结
space-unary-ops通过words、nonwords、overrides三组配置,精确管理一元运算符的空格策略,并将"语法必需空格"与"风格可选空格"区分处理;其实现基于 token 区间比较与canTokensBeAdjacent安全修复保护,测试套件对每个分支、每种运算符组合都给出了可自动修复与不可自动修复的明确预期。理解这些细节,无论是继续使用旧版规则、还是迁移到 ESLint Stylistic,都能确保配置行为完全可控。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考