ESLintlines-between-class-members规则详解:精确控制类成员间的空行布局
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
lines-between-class-members是 ESLint 内置的 layout(布局)类规则,用于强制或禁止 class 成员(字段与方法)之间的空行,帮助开发者保持类结构的视觉分组与可读性。本文基于当前仓库的官方文档、规则实现源码与单元测试,完整讲解该规则的两种配置模式(字符串模式与enforce精细模式)、第二选项exceptAfterSingleLine的语义,以及底层基于 Token 的空白判断原理,读完即可在真实项目中落地这套类内排版规范。
规则概览
该规则的核心作用是通过约束类成员之间的空行数量来提升代码可读性。它只关心"成员与成员之间"的间隔,而不会检查第一个成员之前的空行,也不会检查最后一个成员之后的空行——这一部分由同仓库中的 padded-blocks 规则负责(该规则通过{ "classes": "always" }等选项控制类体开头与结尾的留白)。两者一内一外,共同构成完整的类排版约束。
从当前仓库源码看,该规则在 lib/rules/lines-between-class-members.js 中实现,并通过 lib/rules/index.js 以懒加载方式注册为"lines-between-class-members"。规则的元数据定义如下(摘自 规则源码):
type: "layout":属于布局类规则,不涉及逻辑正确性;fixable: "whitespace":支持自动修复,--fix可以自动补插或删除空行;- 非
recommended规则,需要显式配置开启。
基本行为与代码示例
启用方式("always"为默认值,即使不写也等同于开启):
/* eslint lines-between-class-members: ["error", "always"] */不正确的代码(成员之间没有空行):
/* eslint lines-between-class-members: ["error", "always"]*/ class MyClass { x; foo() { //... } bar() { //... } }正确的代码(每个成员之间都有一行空行):
/* eslint lines-between-class-members: ["error", "always"]*/ class MyClass { x; foo() { //... } bar() { //... } }值得注意的一个边界示例:在无分号风格(semicolon-less style)下,如果前一个字段以分号结尾、且该分号单独出现在下一行行首,规则会将这个分号视为下一个成员的一部分,从而正确判断空行。以下代码在该规则下是正确的:
/* eslint lines-between-class-members: ["error", "always"]*/ class MyClass { x = 1 ;in = 2 }选项详解
该规则接受两个选项,第一个选项可以是字符串或对象,第二个选项是对象:
第一个选项:字符串"always"/"never"
| 取值 | 说明 |
|---|---|
"always"(默认) | 要求每个类成员之后有一个空行 |
"never" | 禁止类成员之后出现空行 |
字符串模式实际上会被内部转换为{ blankLine: <选项值>, prev: "*", next: "*" }的配置项,即"对任意前后成员组合都生效"(见 源码)。
"always"的错误示例(缺少空行):
/* eslint lines-between-class-members: ["error", "always"]*/ class Foo{ x; bar(){} baz(){} }"never"的错误示例(不应出现空行):
/* eslint lines-between-class-members: ["error", "never"]*/ class Bar{ x; bar(){} baz(){} }"always"的正确示例:
/* eslint lines-between-class-members: ["error", "always"]*/ class Foo{ x; bar(){} baz(){} }"never"的正确示例:
/* eslint lines-between-class-members: ["error", "never"]*/ class Bar{ x; bar(){} baz(){} }第一个选项:对象enforce(精细匹配模式)
当需要对不同类别的成员组合施加不同规则时,使用对象形式。它含有一个enforce属性,其值是一个对象数组,每个对象包含三个必填属性:
| 属性 | 可选值 | 说明 |
|---|---|---|
blankLine | "always"/"never" | 要求或禁止指定成员对之间出现空行 |
prev | "method"/"field"/"*" | 前一个类成员的类型;method指方法,field指类字段,*匹配任意成员 |
next | "method"/"field"/"*" | 后一个类成员的类型,取值同prev |
关键规则(见 官方文档 与 源码):
- 可以任意数量地配置;
- 如果一个成员对同时匹配多条配置,以最后一条匹配的配置为准(源码中
getPaddingType从配置数组尾部向前遍历,命中即返回); - 如果一个成员对不匹配任何配置,则跳过不检查。
示例一:仅禁止方法之间出现空行
// disallows blank lines between methods /*eslint lines-between-class-members: [ "error", { enforce: [ { blankLine: "never", prev: "method", next: "method" } ] }, ]*/ class MyClass { constructor(height, width) { this.height = height; this.width = width; } fieldA = 'Field A'; #fieldB = 'Field B'; method1() {} get area() { return this.method1(); } method2() {} }这段代码不正确,因为method1()与get area()、get area()与method2()之间都属于"method → method"组合,却被空行隔开。
示例二:要求字段周围有空行、同时禁止方法之间有空行
// requires blank lines around fields, disallows blank lines between methods /*eslint lines-between-class-members: [ "error", { enforce: [ { blankLine: "always", prev: "*", next: "field" }, { blankLine: "always", prev: "field", next: "*" }, { blankLine: "never", prev: "method", next: "method" } ] }, ]*/ class MyClass { constructor(height, width) { this.height = height; this.width = width; } fieldA = 'Field A'; #fieldB = 'Field B'; method1() {} get area() { return this.method1(); } method2() {} }这段代码不正确:constructor与fieldA之间(*→field组合)缺少空行,fieldA与#fieldB、#fieldB与method1()之间也缺少空行(field→*组合)。
对应的正确版本如下(字段被空行包围,方法之间则保持紧凑):
// requires blank lines around fields, disallows blank lines between methods /*eslint lines-between-class-members: [ "error", { enforce: [ { blankLine: "always", prev: "*", next: "field" }, { blankLine: "always", prev: "field", next: "*" }, { blankLine: "never", prev: "method", next: "method" } ] }, ]*/ class MyClass { constructor(height, width) { this.height = height; this.width = width; } fieldA = 'Field A'; #fieldB = 'Field B'; method1() {} get area() { return this.method1(); } method2() {} }第二个选项:exceptAfterSingleLine
第二个选项是对象,仅含一个布尔属性exceptAfterSingleLine:
| 取值 | 说明 |
|---|---|
false(默认) | 单行类成员之后同样要求空行,不豁免 |
true | 跳过对单行类成员之后空行的检查(多行成员之后仍要求空行) |
正确示例(开启exceptAfterSingleLine后,单行成员x、bar(){}之后无需空行,但多行成员baz(){...}之后仍必须有空行):
/* eslint lines-between-class-members: ["error", "always", { "exceptAfterSingleLine": true }]*/ class Foo{ x; // single line class member bar(){} // single line class member baz(){ // multi line class member } qux(){} }从 源码 看,判断"单行成员"的依据是:成员的第一个 Token 与最后一个 Token 是否位于同一行(!astUtils.isTokenOnSameLine(curFirst, curLast)),随后通过!isMulti && options[1].exceptAfterSingleLine决定是否跳过"always"检查。
组合使用:enforce+exceptAfterSingleLine
两者可以叠加使用。下面的配置要求方法前后有空行、字段之间有空行,同时豁免单行成员之后的空行检查:
/*eslint lines-between-class-members: [ "error", { enforce: [ { blankLine: "always", prev: "*", next: "method" }, { blankLine: "always", prev: "method", next: "*" }, { blankLine: "always", prev: "field", next: "field" } ] }, { exceptAfterSingleLine: true } ]*/ class MyClass { constructor(height, width) { this.height = height; this.width = width; } fieldA = 'Field A'; #fieldB = 'Field B'; method1() {} get area() { return this.method1(); } method2() {} }该示例是正确的:fieldA与#fieldB、#fieldB与method1()之间虽然紧邻,但fieldA、#fieldB、method1()都是单行成员,被exceptAfterSingleLine: true豁免;而多行的get area()之后仍保留了空行。
源码实现原理:Token 边界与空行判定
该规则在ClassBody访问器中遍历body数组,对每对相邻成员执行检查(见 源码)。理解其实现有助于预判各类边界行为:
成员类型匹配:源码定义了三类匹配器(lib/rules/lines-between-class-members.js#L23-L27)——
"*"恒真;field要求节点类型为PropertyDefinition;method要求节点类型为MethodDefinition。这也说明enforce模式下的prev/next是基于 AST 节点类型判断的。边界 Token 的确定:
getBoundaryTokens默认取"当前节点最后一个 Token"与"下一节点第一个 Token"作为边界。唯一的例外是无分号风格:如果当前节点以分号结尾、且该分号与前一 Token 不在同一行、却与下一成员的第一个 Token 在同一行(如x = 1换行后;in = 2),则把分号视为下一个成员的一部分(见 源码注释与实现),这正是文档中那个特殊正确示例的原理。连续 Token 的合并:
findLastConsecutiveTokenAfter与findFirstConsecutiveTokenBefore会向前/向后吞并行距不超过阈值(此处为 1 行)的连续 Token,并包含注释,从而把位于两成员之间的注释纳入空行区域计算。空行判定:通过
afterPadding.loc.start.line - beforePadding.loc.end.line > 1判断是否存在空行;hasTokenOrCommentBetween用于检测边界之间是否还夹着注释或 Token。修复逻辑(fixer):
"never"且存在空行时,用\n替换边界之间的文本区间,删除多余空行;"always"且无空行、且未被exceptAfterSingleLine豁免时,在curLineLastToken之后插入一个\n;- 若空行区域中夹着注释或 Token,则返回
null不自动修复(避免破坏注释排版,见 lib/rules/lines-between-class-members.js#L326-L351)。
测试用例佐证
该规则的完整行为由 tests/lib/rules/lines-between-class-members.js(共 2600+ 行)验证,测试基于ecmaVersion: 2022运行。以下几类关键场景均有覆盖:
- 字符串模式:
"always"的自动修复输出(如class foo{ bar(){}\nbaz(){}}修复为中间加空行)、"never"的删除空行修复; - 注释与分号:成员间夹
//、/* */注释、孤立分号;、;;的多种组合; - 字段与私有字段:
field1、#field1、field1 = () => {...}等字段场景; enforce组合:对method→method、method→field、field→method、field→field、*→method、method→*、*→field、field→*、*→*等所有 prev/next 组合的 valid/invalid 用例;- 多配置优先级:测试中特意把
"never"系列配置写在"always"系列之前,验证"后写的配置优先"(最后一个匹配的配置生效)的语义(见测试文件中"multiple configurations"段落); exceptAfterSingleLine组合:验证单行成员豁免、多行成员仍被检查的行为。
规则的弃用与迁移
需要特别说明:该规则在ESLint v8.53.0 起被标记为弃用,availableUntil: "11.0.0",原因是格式化类规则正在被移出 ESLint 核心(见 lib/rules/lines-between-class-members.js#L35-L56)。官方推荐迁移至ESLint Stylistic维护的替代实现,即@stylistic/eslint-plugin中的同名规则lines-between-class-members,配置语法保持一致。如果你正在维护旧项目且无法升级,该规则在当前核心版本中依然可用;若为新项目,建议直接采用 ESLint Stylistic 方案。
何时不使用该规则
如果你不希望强制类成员之间存在空行,直接关闭此规则即可:
/* eslint lines-between-class-members: "off" */兼容性
该规则的灵感来源是 JSCS 的两条规则:
requirePaddingNewLinesAfterBlocksdisallowPaddingNewLinesAfterBlocks
这两条规则只提供了"始终要求/始终禁止"的二元能力,而 ESLint 的lines-between-class-members通过enforce对象扩展出了按成员类型(方法/字段/任意)与方向(prev/next)精细配置的能力,同时补上了exceptAfterSingleLine单行豁免选项,功能上更贴近现代 class 语法(字段、私有字段、getter 等)的实际排版需求。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考