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)描述的是:相对于控制语句(if、while、for、try等)及其代码块主体,花括号{}应该放置在什么位置。它与编程中的缩进风格(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中的else、try-catch中的catch与finally必须独占一行,位于前一个右花括号之后:
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 要求块的左花括号与控制语句位于同一行,同时else、catch、finally等关键字必须跟在右花括号的同一行之后。
使用默认"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的左花括号未与控制语句同行、try后catch前的换行、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之后换行再接if(else\nif (bar) { ... })。
四、stroustrup:独立成行的 else/catch/finally
Stroustrup 风格与 1tbs 唯一的区别,在于else、catch、finally必须独占一行,出现在前一个右花括号之后,而不是与之同行。
使用"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左花括号换行、try与catch同行、类体与静态块左花括号换行,以及最后的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 下,else、catch必须与前面的右花括号分行,但左花括号仍与控制语句同行——这是它与 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. 覆盖的节点类型
规则通过BlockStatement、StaticBlock、ClassBody、SwitchStatement、IfStatement、TryStatement六个 visitor 覆盖所有可能出现大括号的位置(见 lib/rules/brace-style.js):
BlockStatement:只有当块的父节点不是语句列表容器时才校验——这是为了避免对"裸块"(如{ foo(); }这种用作语句列表的块)重复报错,判定依据是 lib/rules/utils/ast-utils.js 中的STATEMENT_LIST_PARENTS集合(包含Program、BlockStatement、StaticBlock、SwitchCase);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 前后插入换行。
从配置元数据看,该规则不支持 suggestions(hasSuggestions: 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-style自ESLint 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前需要回答的问题清单:
- 团队选择哪种风格?——
1tbs(默认,最贴合 JavaScript 社区主流习惯)、stroustrup(else独立成行的 1tbs 变体)还是allman(全括号换行)? - 是否允许单行块?——若希望保留
if (foo) { bar(); }这类紧凑写法,务必设置{ "allowSingleLine": true },否则默认false会将其判为错误; - 是否交给
--fix自动修复?——规则完全可自动修复,建议在 CI 或提交钩子中运行eslint --fix,把风格问题消灭在代码合入之前; - 是否已迁移到 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),仅供参考