ESLint space-in-parens 规则详解:括号内侧空格的一致化控制与源码级剖析
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
space-in-parens是 ESLint 核心库中的一条布局类(layout)规则,用于统一圆括号()内侧空格的书写风格——要么强制要求括号内侧有空格,要么强制要求没有空格。本文基于 ESLint 仓库的官方规则文档,结合 规则实现 与 测试用例 的源码证据,完整讲解该规则的两种主选项、四种异常(exceptions)机制的精确语义,以及其可自动修复(fixable)的底层实现原理,帮助你准确配置并理解这一格式化规则。
规则要解决的问题
不同的代码风格指南对括号内侧空格的取舍并不一致。有的风格要求括号内侧保留空格:
foo( 'bar' ); var x = ( 1 + 2 ) * 3;而另一些风格则要求括号内侧不出现空格:
foo('bar'); var x = (1 + 2) * 3;space-in-parens就是用来强制统一这种风格的工具:它会检查每个(右侧以及每个)左侧是否含有一个或多个空格,并根据配置决定是禁止还是要求这些空格。只要没有显式使用"empty"异常来禁止空括号,()本身始终是允许的。
规则分类与关联规则
从 规则文档 的元数据可以看到,该规则的rule_type为layout,在 ESLint 的分类体系中属于代码布局与格式类规则。它与另外两条括号相关规则互为补充:
- array-bracket-spacing:控制方括号
[]内侧空格; - object-curly-spacing:控制花括号
{}内侧空格; - computed-property-spacing:控制计算属性访问时方括号内侧的空格。
值得强调的是:space-in-parens只检查圆括号内侧的空格,不会去管花括号或方括号内侧是否有空格;只有当{}、[]恰好紧邻某个左括号或右括号时,它才会对这两类括号与圆括号之间的位置关系施加约束(这正是 exceptions 机制的用武之地,下文详述)。
配置选项:never 与 always
space-in-parens有两个选项,通过配置数组的第二个位置传入:
| 选项 | 含义 | 默认 |
|---|---|---|
"never" | 括号内侧不允许有空格 | ✅ 默认 |
"always" | 括号内侧必须有空格 | ❌ |
在 ESLint 配置文件中按如下方式指定:
"space-in-parens": ["error", "always"]第二个选项是数组的第一项;而第三项是可选的 exceptions 对象(见后文"异常机制"一节)。
"never"(默认)的行为
使用默认的"never"选项时,以下代码均为错误:
/*eslint space-in-parens: ["error", "never"]*/ foo( ); foo( 'bar'); foo('bar' ); foo( 'bar' ); foo( /* bar */ ); var foo = ( 1 + 2 ) * 3; ( function () { return 'bar'; }() );以下代码在"never"下是正确的:
/*eslint space-in-parens: ["error", "never"]*/ foo(); foo('bar'); foo(/* bar */); var foo = (1 + 2) * 3; (function () { return 'bar'; }());注意foo(/* bar */):行内块注释紧贴括号是允许的,因为注释本身不是"空格"。但foo( /* bar */ )中注释两侧出现了真正的空白字符,因此会被判定为错误。
"always" 的行为
配置为"always"时,以下代码均为错误:
/*eslint space-in-parens: ["error", "always"]*/ foo( 'bar'); foo('bar' ); foo('bar'); foo(/* bar */); var foo = (1 + 2) * 3; (function () { return 'bar'; }());而以下代码在"always"下是正确的:
/*eslint space-in-parens: ["error", "always"]*/ foo(); foo( ); foo( 'bar' ); foo( /* bar */ ); var foo = ( 1 + 2 ) * 3; ( function () { return 'bar'; }() );两个细节值得注意:
- 空括号
()在两个选项下都被允许——只要没有显式配置"empty"异常。所以在"always"下,foo()与foo( )都是合法的。 - 在
"always"下,foo(/* bar */)会被报告,因为(与注释之间缺少空格;修复后会变成foo( /* bar */ )。
异常机制(Exceptions)
为了让规则更加灵活,可以在配置数组的第三项传入一个对象,用"exceptions"键指定一组例外,其值为字符串数组:
"space-in-parens": ["error", "always", { "exceptions": ["{}"] }]可用的异常值共有四个:["{}", "[]", "()", "empty"]。
异常的工作方式是在第一选项的语境下取反:
- 如果第一项是
"always"(要求括号内侧有空格),那么某个异常出现的位置将被禁止出现空格; - 如果第一项是
"never"(禁止括号内侧有空格),那么某个异常出现的位置将被强制要求有空格。
也就是说,异常描述的是"紧邻括号的那个 token 是什么",并据此反转该处的空格要求。
异常 "{}":紧邻花括号
当配置为"never", { "exceptions": ["{}"] }时,以下代码为错误(因为{紧邻(,此时反而要求有空格,而这里没有):
/*eslint space-in-parens: ["error", "never", { "exceptions": ["{}"] }]*/ foo({bar: 'baz'}); foo(1, {bar: 'baz'});以下代码为正确:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["{}"] }]*/ foo( {bar: 'baz'} ); foo(1, {bar: 'baz'} );注意第二个正确示例foo(1, {bar: 'baz'} ):{前有空格,同时)前也有空格——因为异常针对的是"紧邻括号的花括号",规则只反转花括号所在那一侧的要求。
当配置为"always", { "exceptions": ["{}"] }时,语义正好反转。以下代码为错误:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}"] }]*/ foo( {bar: 'baz'} ); foo( 1, {bar: 'baz'} );以下代码为正确:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}"] }]*/ foo({bar: 'baz'}); foo( 1, {bar: 'baz'});异常 "[]":紧邻方括号
"never", { "exceptions": ["[]"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["[]"] }]*/ foo([bar, baz]); foo([bar, baz], 1);以下代码为正确:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["[]"] }]*/ foo( [bar, baz] ); foo( [bar, baz], 1);"always", { "exceptions": ["[]"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["[]"] }]*/ foo( [bar, baz] ); foo( [bar, baz], 1 );以下代码为正确:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["[]"] }]*/ foo([bar, baz]); foo([bar, baz], 1 );这里foo([bar, baz], 1 )之所以正确,是因为[紧邻(处无需空格(异常生效),而)前的空格则按"always"保留。
异常 "()":嵌套圆括号
"never", { "exceptions": ["()"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["()"] }]*/ foo((1 + 2)); foo((1 + 2), 1); foo(bar());以下代码为正确:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["()"] }]*/ foo( (1 + 2) ); foo( (1 + 2), 1); foo(bar() );"always", { "exceptions": ["()"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["()"] }]*/ foo( ( 1 + 2 ) ); foo( ( 1 + 2 ), 1 );以下代码为正确:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["()"] }]*/ foo(( 1 + 2 )); foo(( 1 + 2 ), 1 );异常 "empty":空括号
"empty"异常专门处理空括号(),其工作机制与其他异常一致——反转第一项选项:
"always"同时允许()和( )(未配置异常时);"never"(默认)要求();"always"加上exceptions: ["empty"]时,要求()(此时( )反而被禁止);"never"加上exceptions: ["empty"]时,要求( )(不带空格的空括号此时被禁止)。
"never", { "exceptions": ["empty"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["empty"] }]*/ foo();以下代码为正确:
/*eslint space-in-parens: ["error", "never", { "exceptions": ["empty"] }]*/ foo( );"always", { "exceptions": ["empty"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["empty"] }]*/ foo( );以下代码为正确:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["empty"] }]*/ foo();组合多个异常
"exceptions"数组中可以同时包含多个条目,且多个异常在同一括号上可能同时生效。
"always", { "exceptions": ["{}", "[]"] }下,以下代码为错误:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}", "[]"] }]*/ bar( {bar:'baz'} ); baz( 1, [1,2] ); foo( {bar: 'baz'}, [1, 2] );以下代码为正确:
/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}", "[]"] }]*/ bar({bar:'baz'}); baz( 1, [1,2]); foo({bar: 'baz'}, [1, 2]);何时不使用此规则
如果你并不关心括号内侧空格的书写一致性,可以直接关闭该规则(在配置中设为"off"或从配置中移除)。它纯粹是风格层面的约束,对代码运行行为没有任何影响。
源码级剖析:规则的实现原理
理解了配置语义后,再来看 lib/rules/space-in-parens.js 的具体实现,能更深刻地把握"异常取反"为何如此工作。
规则元信息
实现文件头部声明了该规则的元信息(meta):
type: "layout":属于布局类规则,与文档元数据一致;fixable: "whitespace":声明该规则产生的报告可通过--fix自动修复,修复只涉及空白字符的增删;docs.recommended: false:未纳入推荐配置集;schema精确约束了配置结构:- 第一项为枚举
["always", "never"]; - 第二项为对象,仅允许
exceptions属性,其值为字符串数组,元素必须属于枚举["{}", "[]", "()", "empty"],且uniqueItems: true(数组内不允许重复),additionalProperties: false拒绝任何未知属性。
- 第一项为枚举
四个报告消息也定义在 meta 中:
missingOpeningSpace:"There must be a space after this paren."missingClosingSpace:"There must be a space before this paren."rejectedOpeningSpace:"There should be no space after this paren."rejectedClosingSpace:"There should be no space before this paren."
选项解析与异常映射
create函数首先解析选项:ALWAYS = context.options[0] === "always",并从context.options[1].exceptions中读取异常数组。随后将四个异常字符串映射为四个布尔开关(braceException、bracketException、parenException、empty),再通过getExceptions()展开为 openers/closers 两组 token 值列表:
"{}"→ openers 含{,closers 含};"[]"→ openers 含[,closers 含];"()"→ openers 含(,closers 含);"empty"→ openers 含),closers 含((实现上的巧妙处理:空括号场景下,(的后继 token 是),)的前驱 token 是(,因此把)当作 openers 的例外、(当作 closers 的例外)。
检测流程
规则的Program访问器在整份文件的词法层级上工作:它取sourceCode.tokensAndComments(token 与注释的混合序列),逐个遍历;对每个 token,用 ast-utils.js 中的isOpeningParenToken/isClosingParenToken(其判定条件是token.value === "("(或")")且token.type === "Punctuator")识别圆括号,然后分别对(与)各做两次检查:
- 缺少空格(missing):开括号后 / 闭括号前没有空格,且该处要求有空格;
- 多余空格(rejected):开括号后 / 闭括号前有空格,且该处禁止有空格。
关键判断逻辑中,"是否真的有空白"由sourceCode.isSpaceBetween(left, right)判定(该能力定义于 source-code.js,并被space-before-blocks、keyword-spacing、block-spacing等大量间距类规则共用);"token 是否位于同一行"由isTokenOnSameLine判定。
几个容易误判的场景在实现中被显式规避:
- 跨行不算多余空格:
openerRejectsSpace/closerRejectsSpace首先检查两个 token 是否在同一行,不在同一行直接返回 false,因此换行后的括号不会被误报; - 行注释不算空格:如果
(后紧跟的是Line类型的注释(即//行注释),同样不会被判定为多余空格——测试用例foo( //some comment\nbar\n)验证了这一点; - 空括号默认放行:当
options.empty为假时,(后紧跟)、或)前紧跟((即()空括号)都会直接返回"不缺空格",这正是"只要不配置"empty"异常,()始终允许"的实现根源。
自动修复的实现
规则声明了fixable: "whitespace",因此每个报告都附带 fixer:
- 缺少开括号后空格 →
fixer.insertTextAfter(token, " "); - 缺少闭括号前空格 →
fixer.insertTextBefore(token, " "); - 多余空格 →
fixer.removeRange([token.range[1], nextToken.range[0]])(精确删除两个 token 之间的空白区间)。
测试用例中大量验证了这些修复,例如bar( baz )在"never"下被修复为bar(baz),foo(bar)在"always"下被修复为foo( bar ),foo( )在"never", { "exceptions": ["empty"] }下被修复为foo( )的反向场景等。
测试覆盖:规则语义的完整验证
测试文件 使用 ESLint 的 RuleTester 对规则进行了非常详尽的验证,覆盖了:
- 两种主选项下的空括号、单参数、多参数、算术表达式、变量声明等基础场景;
- 多行与缩进场景,如
foo\n(\nbar\n)在"always"和"never"下均为合法(验证跨行不报多余空格); - 注释场景:块注释、行注释与括号的多种组合,如
foo( /* bar */ )、foo(/* bar */ baz)等; - 四种异常的全部组合,包括单异常、双异常(如
exceptions: ["{}", "[]"])、乃至全异常(["{}", "[]", "()", "empty"])同时生效的情况; - 异常与空括号的组合,如
"never", { "exceptions": ["empty"] }要求foo( ); - ES6 模板字符串:
var foo = \(bar ${( 1 + 2 )})`;等用例验证了模板字面量内的表达式插值也会被正确检查(需配置languageOptions: { ecmaVersion: 6 }`); - 冗余/空配置:
{ exceptions: [] }、空对象{}等"没有实际异常"的配置也被验证不会产生异常效果。
这些测试同时是理解规则边界的最佳参考:例如嵌套括号( ( 1 + 2 ) )在"never"下会报告 4 个错误(两层括号的左右两侧各一处),而配合exceptions: ["[]"]时错误数量不变(因为[]异常与圆括号无关),这印证了异常只作用于"紧邻圆括号的对应 token"这一精确语义。
注意事项:规则的弃用状态
需要特别留意的是,从源码中的meta.deprecated可以看到,space-in-parens已自ESLint v8.53.0起被标记为弃用,计划可用至v11.0.0。弃用的原因是格式化类规则正在从 ESLint 核心中迁出,交由ESLint Stylistic(@stylistic/eslint-plugin)继续维护,该插件中提供同名的space-in-parens规则作为替代。这意味着:
- 在新项目中使用该规则时,建议直接采用
@stylistic/eslint-plugin的版本,以获得长期维护; - 若在既有 ESLint 8/9/10 项目中继续使用,请知悉其将在 v11.0.0 后从核心中移除,届时需要完成迁移。
小结
space-in-parens通过"never"/"always"两个选项与"{}"、"[]"、"()"、"empty"四种异常的组合,为圆括号内侧空格提供了从"全局统一"到"按括号内容精细差异化"的完整控制粒度。理解其"异常在"always"下禁止空格、在"never"下强制空格"的取反语义,是正确配置它的关键。配合--fix自动修复,团队可以在不改动编码习惯的前提下,将存量代码一键收敛到统一的括号间距风格。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考