ESLint brace-style 规则完全指南:1tbs、Stroustrup 与 Allman 三种大括号风格详解
2026/9/11 22:14:34 网站建设 项目流程

ESLint brace-style 规则完全指南:1tbs、Stroustrup 与 Allman 三种大括号风格详解

【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint

本指南以 ESLint 内置规则brace-style(规则文档)为核心,系统讲解 JavaScript 中大括号(curly braces)的三种主流排版风格——One True Brace Style(1tbs)、Stroustrup 与 Allman,并给出每种风格的完整配置示例、正确/错误代码对照以及自动修复(--fix)能力说明。读完本文,你将能够为团队项目选定并落地统一的块级大括号风格,理解该规则在 lib/rules/brace-style.js 中的底层判定逻辑,并了解其在 ESLint 8.53.0 之后被迁移至 ESLint Stylistic 生态的现状与迁移方式。

一、什么是大括号风格

在编程语言中,大括号风格(brace style)描述的是:相对于控制语句(ifwhilefortry等)及其代码块主体,花括号{}应该放置在什么位置。它与编程中的缩进风格(indent style)紧密相关。全世界存在十几种甚至更多的大括号风格变体,而 ESLint 的brace-style规则从中选出了三种最具代表性的风格进行强制约束:

风格左花括号位置右花括号后的else/catch/finally位置
1tbs(默认)与控制语句同一行与前一个右花括号同一行
stroustrup与控制语句同一行独立成行,位于前一个右花括号之后
allman独立成行独立成行

虽然没有任何一种风格在客观上"优于"其他风格,但绝大多数开发者都认同:在一个项目内保持一致的大括号风格,对代码的长期可维护性至关重要。这正是本规则存在的意义——用自动化检查代替人工讨论,把风格决策固化为项目规范。

三种风格直观对比

用同一段if-else逻辑展示三种风格的差异:

1tbs(One True Brace Style)——JavaScript 中最常见,块的左花括号与其对应的语句或声明放在同一行:

if (foo) { bar(); } else { baz(); }

Stroustrup——1tbs 的常见变体,区别在于if-else中的elsetry-catch中的catchfinally必须独占一行,位于前一个右花括号之后:

if (foo) { bar(); } else { baz(); }

Allman——所有花括号都期望独占一行,且不附加额外缩进:

if (foo) { bar(); } else { baz(); }

二、规则配置:Options 详解

brace-style是 ESLint 内置的布局类(layout)规则。根据 lib/rules/brace-style.js 中的 schema 定义,它接受一个字符串选项 + 一个对象选项

schema: [ { enum: ["1tbs", "stroustrup", "allman"] }, { type: "object", properties: { allowSingleLine: { type: "boolean", default: false } }, additionalProperties: false } ]

字符串选项(三种风格)

  • "1tbs"默认):强制 One True Brace Style;
  • "stroustrup":强制 Stroustrup 风格;
  • "allman":强制 Allman 风格。

对象选项(例外规则)

  • "allowSingleLine": true(默认false):允许一个块的开括号和闭括号位于同一行,即允许function nop() { return; }这种单行写法。

注意additionalProperties: false——对象选项只接受allowSingleLine一个键,其他键会直接触发配置校验错误。字符串选项省略时,源码中通过const style = context.options[0] || "1tbs"回退到默认值(见 lib/rules/brace-style.js)。

在 eslint.config.js 中的启用方式(flat config)

// eslint.config.js export default [ { rules: { // 默认 1tbs "brace-style": "error", // 显式指定 1tbs "brace-style": ["error", "1tbs"], // 1tbs + 允许单行块 "brace-style": ["error", "1tbs", { allowSingleLine: true }], // Stroustrup "brace-style": ["error", "stroustrup"], // Allman "brace-style": ["error", "allman"], }, }, ];

"error"表示违反规则时报错并使eslint命令以非零退出码结束;若只想提示不阻断构建,可改为"warn"

三、1tbs:One True Brace Style(默认)

1tbs 要求块的左花括号与控制语句位于同一行,同时elsecatchfinally等关键字必须跟在右花括号的同一行之后。

使用默认"1tbs"错误代码

/*eslint brace-style: "error"*/ function foo() { return true; } if (foo) { bar(); } try { somethingRisky(); } catch(e) { handleError(); } if (foo) { bar(); } else { baz(); } class C { static { foo(); } }

上述代码违反了:函数/if的左花括号未与控制语句同行、trycatch前的换行、else未与前一个右花括号同行、类体与静态块(static block)左花括号的换行。

使用默认"1tbs"正确代码

/*eslint brace-style: "error"*/ function foo() { return true; } if (foo) { bar(); } if (foo) { bar(); } else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } // 没有大括号时,不存在任何问题 if (foo) bar(); else if (baz) boom();

注意最后两行:当块没有使用大括号(如单语句if)时,规则完全不介入,因此if (foo) bar(); else if (baz) boom();在 1tbs 下是合法的。

使用"1tbs", { "allowSingleLine": true }正确代码

/*eslint brace-style: ["error", "1tbs", { "allowSingleLine": true }]*/ function nop() { return; } if (foo) { bar(); } if (foo) { bar(); } else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } if (foo) { baz(); } else { boom(); } if (foo) { baz(); } else if (bar) { boom(); } if (foo) { baz(); } else if (bar) { boom(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } class D { static { foo(); } }

这里展示了allowSingleLine: true的灵活边界:它允许整个块单行(if (foo) { bar(); }),也允许"左半块单行、后续else分支多行"(if (foo) { baz(); } else { boom(); }),甚至允许else之后换行再接ifelse\nif (bar) { ... })。

四、stroustrup:独立成行的 else/catch/finally

Stroustrup 风格与 1tbs 唯一的区别,在于elsecatchfinally必须独占一行,出现在前一个右花括号之后,而不是与之同行。

使用"stroustrup"错误代码

/*eslint brace-style: ["error", "stroustrup"]*/ function foo() { return true; } if (foo) { bar(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } if (foo) { bar(); } else { baz(); }

错误原因:function/if左花括号换行、trycatch同行、类体与静态块左花括号换行,以及最后的else与右花括号同行。

使用"stroustrup"正确代码

/*eslint brace-style: ["error", "stroustrup"]*/ function foo() { return true; } if (foo) { bar(); } if (foo) { bar(); } else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } // 没有大括号时,不存在任何问题 if (foo) bar(); else if (baz) boom();

在 Stroustrup 下,elsecatch必须与前面的右花括号分行,但左花括号仍与控制语句同行——这是它与 1tbs 的关键区别,也是它与 Allman 的关键区别。

使用"stroustrup", { "allowSingleLine": true }正确代码

/*eslint brace-style: ["error", "stroustrup", { "allowSingleLine": true }]*/ function nop() { return; } if (foo) { bar(); } if (foo) { bar(); } else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } class D { static { foo(); } }

allowSingleLine生效后,单行块本身合法;但只要换行,else/catch仍必须保持独立成行。

五、allman:所有花括号独占一行

Allman 风格(又称"BSD 风格")要求所有花括号独占一行,左花括号不与控制语句同行,右花括号后的else/catch也独立成行。

使用"allman"错误代码

/*eslint brace-style: ["error", "allman"]*/ function foo() { return true; } if (foo) { bar(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } if (foo) { bar(); } else { baz(); }

错误原因包括:function左花括号与控制语句同行、if块的右花括号bar(); }与语句同行、catch与前一个右花括号同行、类体/静态块左花括号同行,以及最后一段if (foo) { ... } else { ... }完全不符合 Allman 要求。

使用"allman"正确代码

/*eslint brace-style: ["error", "allman"]*/ function foo() { return true; } if (foo) { bar(); } if (foo) { bar(); } else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } } // 没有大括号时,不存在任何问题 if (foo) bar(); else if (baz) boom();

使用"allman", { "allowSingleLine": true }正确代码

/*eslint brace-style: ["error", "allman", { "allowSingleLine": true }]*/ function nop() { return; } if (foo) { bar(); } if (foo) { bar(); } else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } class C { static { foo(); } static { foo(); } } class D { static { foo(); } }

有趣的是,在 Allman +allowSingleLine下,类中既允许static { foo(); }(单行块),也允许static\n{ foo(); }(多行块)——因为allowSingleLine只对"块内内容"放行,而左花括号自身的位置仍然按 Allman 规则校验。这一点可以在 tests/lib/rules/brace-style.js 的对应 valid 用例中找到印证。

六、源码剖析:规则如何判定大括号位置

brace-style的实现在 lib/rules/brace-style.js,核心思路是基于 token 级别的换行检测,而非语法树节点的深度遍历。规则通过四个检查函数完成判定:

1.validateCurlyPair:校验一对花括号的相对位置

对每一对开闭花括号,规则检查四个维度(见 lib/rules/brace-style.js):

  • 左花括号与控制语句同行:非 Allman 风格下,若左花括号与控制语句 token 不在同一行,报告nextLineOpen("Opening curly brace does not appear on the same line as controlling statement."),修复方式为删除两 token 之间的换行;
  • Allman 下左花括号必须换行:Allman 风格下,若左花括号与控制语句同行且不满足单行例外,报告sameLineOpen,修复方式为在左花括号前插入换行;
  • 块首条语句必须换行:若左花括号与其后第一个 token 同行(且该 token 不是右花括号本身),报告blockSameLine("Statement inside of curly braces should be on next line."),修复方式为在左花括号后插入换行;
  • 右花括号前必须换行:若右花括号与前一个 token 同行,报告singleLineClose,修复方式为在右花括号前插入换行。

所有检查都受singleLineException约束——当开启allowSingleLine且开闭花括号本身在同一行时,上述检查整体豁免。

2.validateCurlyBeforeKeyword:校验关键字前花括号的位置

该函数处理右花括号与后续else/catch/finally关键字的相对位置(见 lib/rules/brace-style.js):

  • 1tbs:若右花括号与关键字不在同一行,报告nextLineClose("Closing curly brace does not appear on the same line as the subsequent block."),修复为删除换行;
  • stroustrup / allman:若右花括号与关键字在同一行,报告sameLineClose,修复为在右花括号后插入换行。

3. 覆盖的节点类型

规则通过BlockStatementStaticBlockClassBodySwitchStatementIfStatementTryStatement六个 visitor 覆盖所有可能出现大括号的位置(见 lib/rules/brace-style.js):

  • BlockStatement:只有当块的父节点不是语句列表容器时才校验——这是为了避免对"裸块"(如{ foo(); }这种用作语句列表的块)重复报错,判定依据是 lib/rules/utils/ast-utils.js 中的STATEMENT_LIST_PARENTS集合(包含ProgramBlockStatementStaticBlockSwitchCase);
  • StaticBlock:跳过static关键字 token 后取花括号校验,覆盖 ES2022 类静态块;
  • ClassBody:校验类体花括号;
  • SwitchStatement:定位switch的花括号对并校验;
  • IfStatement:当if主体是块且存在else分支时,调用validateCurlyBeforeKeyword处理}else之间的换行;
  • TryStatement:依次处理}catch/finally、以及catch块与finally之间的换行。

4. 自动修复:fixable: "whitespace"

规则声明fixable: "whitespace"(见 lib/rules/brace-style.js),意味着所有报告的问题都可被eslint --fix自动修复。修复手段包括:

  • removeNewlineBetween:删除两个 token 之间的换行——但若两 token 之间含有注释,则放弃修复(返回null),避免破坏注释(见 lib/rules/brace-style.js);
  • insertTextBefore/insertTextAfter:在 token 前后插入换行。

从配置元数据看,该规则不支持 suggestionshasSuggestions: false,见 docs/src/_data/rules.json),只有fix,没有手动建议选项。

七、相关规则与联动

brace-style只关心花括号本身的位置,它不校验花括号内部与外部的空格。与它密切相关的两个布局规则分别是:

  • block-spacing:强制花括号内部(左花括号右侧、右花括号左侧)的空格规则,即{ foo }还是{foo}
  • space-before-blocks:强制花括号之前的空格规则,即function foo(){还是function foo() {

三者各管一段,配合使用可以完整约束大括号的排版:brace-style管位置(同行还是换行)、space-before-blocks管左花括号前的空格、block-spacing管花括号内的空格。

八、当不需要使用此规则

如果你不希望强制团队采用某一种特定的大括号风格(例如项目由不同背景的开发者协作、代码风格允许混用),那么不要启用此规则即可。ESLint 的规则设计哲学是"按需启用"——规则只服务于团队明确想要约束的规范。

九、迁移提示:该规则在核心中的弃用现状

需要特别说明的是,根据 lib/rules/brace-style.js 中的弃用标记,brace-styleESLint v8.53.0起在 ESLint 核心中被标记为弃用(deprecated),并计划在 v11.0.0 之前从核心中移除。这是 ESLint 将格式化类规则整体移出核心计划的一部分——此前核心内约 40 个格式化规则都被逐步迁移至ESLint Stylistic生态(@stylistic/eslint-plugin,对应规则名为@stylistic/brace-style)。

迁移方式:从@stylistic/eslint-plugin引入该规则替换核心配置。若你的项目正在使用旧版 ESLint(8.53.0 之前)或尚未升级,核心中的brace-style依然可以正常工作;但新项目建议直接使用 Stylistic 插件的同名规则,以跟上格式化规则的演进方向。相关元数据可参考 docs/src/_data/rules_meta.json 与 docs/src/_data/rules.json。

十、快速自查清单

为便于团队落地,这里整理一份启用brace-style前需要回答的问题清单:

  1. 团队选择哪种风格?——1tbs(默认,最贴合 JavaScript 社区主流习惯)、stroustrupelse独立成行的 1tbs 变体)还是allman(全括号换行)?
  2. 是否允许单行块?——若希望保留if (foo) { bar(); }这类紧凑写法,务必设置{ "allowSingleLine": true },否则默认false会将其判为错误;
  3. 是否交给--fix自动修复?——规则完全可自动修复,建议在 CI 或提交钩子中运行eslint --fix,把风格问题消灭在代码合入之前;
  4. 是否已迁移到 Stylistic?——ESLint 8.53.0 之后核心规则已弃用,新项目请直接配置@stylistic/eslint-plugin中的@stylistic/brace-style

【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint

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

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

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

立即咨询