news 2026/9/12 16:10:20

ESLint space-in-parens 规则详解:括号内侧空格的一致化控制与源码级剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESLint space-in-parens 规则详解:括号内侧空格的一致化控制与源码级剖析

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_typelayout,在 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'; }() );

两个细节值得注意:

  1. 空括号()在两个选项下都被允许——只要没有显式配置"empty"异常。所以在"always"下,foo()foo( )都是合法的。
  2. "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中读取异常数组。随后将四个异常字符串映射为四个布尔开关(braceExceptionbracketExceptionparenExceptionempty),再通过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")识别圆括号,然后分别对()各做两次检查:

  1. 缺少空格(missing):开括号后 / 闭括号前没有空格,且该处要求有空格;
  2. 多余空格(rejected):开括号后 / 闭括号前有空格,且该处禁止有空格。

关键判断逻辑中,"是否真的有空白"由sourceCode.isSpaceBetween(left, right)判定(该能力定义于 source-code.js,并被space-before-blockskeyword-spacingblock-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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 16:00:27

立体仓库自动化控制系统开发与三菱PLC组态王应用

1. 立体仓库自动化控制系统的核心组成这个3行3列9仓位立体仓库控制系统,本质上是一个典型的工业自动化项目,核心在于通过组态王软件实现仓库的虚拟仿真,并与三菱PLC建立OPC通讯。我在自动化行业做了十几年,这种立体仓库的控制系统…

作者头像 李华