ESLint lines-around-comment 规则详解:强制注释前后空行以提升代码可读性
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
lines-around-comment是 ESLint 内置的布局(layout)类规则,用于强制在块注释(/* */)与行注释(//)的前后保留空行,使注释与代码在视觉上清晰分层。本文以 规则文档 为核心,结合 规则源码 与 规则测试,系统讲解该规则的全部 15 个可配置项、默认值、判定逻辑与源码实现,帮助你精确控制注释周围的空行格式。
Rule Details:规则做了什么
许多团队风格指南要求在注释前后保留空行,本规则的目标正是让注释更易读、提升代码可读性。具体来说,规则要求:
- 在块注释(
/* ... */)和/或行注释(// ...)之前或之后保留空行; beforeBlockComment与afterBlockComment分别控制块注释前后的空行,beforeLineComment与afterLineComment分别控制行注释前后的空行,两类注释互不影响,可按需单独启用;- 不检查与代码同行出现的注释(如
foo(); // 行尾注释),也不要求文件开头或结尾出现空行。
从源码看,规则在Program节点上遍历sourceCode.getAllComments()获取的所有注释 token,并按token.type分流处理:lib/rules/lines-around-comment.js#L546-L579 中,"Line"类型走beforeLineComment/afterLineComment逻辑,"Block"类型走beforeBlockComment/afterBlockComment逻辑,"Shebang"类型(即#!注释)则仅在启用afterHashbangComment时处理。
Options:完整的可配置项
本规则接受一个对象选项,下表汇总了所有参数、默认值与作用(与源码 schema 定义 完全一致):
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
beforeBlockComment | boolean | true | 要求块注释前有空行 |
afterBlockComment | boolean | false | 要求块注释后有空行 |
beforeLineComment | boolean | false | 要求行注释前有空行 |
afterLineComment | boolean | false | 要求行注释后有空行 |
allowBlockStart | boolean | false | 允许注释出现在块语句、函数体、类、switch 语句及类静态块(static block)的起始位置 |
allowBlockEnd | boolean | false | 允许注释出现在上述块结构(块语句、函数体、类、switch、类静态块)的结尾位置 |
allowObjectStart | boolean | false | 允许注释出现在对象字面量(含解构模式)的起始位置 |
allowObjectEnd | boolean | false | 允许注释出现在对象字面量(含解构模式)的结尾位置 |
allowArrayStart | boolean | false | 允许注释出现在数组字面量(含解构模式)的起始位置 |
allowArrayEnd | boolean | false | 允许注释出现在数组字面量(含解构模式)的结尾位置 |
allowClassStart | boolean | false | 允许注释出现在类的起始位置(未设置时沿用allowBlockStart的行为) |
allowClassEnd | boolean | false | 允许注释出现在类的结尾位置(未设置时沿用allowBlockEnd的行为) |
ignorePattern | string | 无 | 自定义被规则忽略的注释正则模式 |
applyDefaultIgnorePatterns | boolean | true | 是否应用默认忽略模式 |
afterHashbangComment | boolean | false | 要求 hashbang 注释(#!...)后有空行 |
注意:beforeBlockComment的默认值true是通过源码显式兜底实现的——lib/rules/lines-around-comment.js#L164-L167 中,当该项为undefined时会被赋值为true;其余三个before*/after*开关默认关闭。allowClassStart与allowClassEnd比较特殊:schema 中未声明默认值,其生效逻辑与allowBlockStart/allowBlockEnd联动(详见下文"类边界判定")。
配置示例(eslint.config.js的 rules 字段):
export default [ { rules: { "lines-around-comment": ["error", { beforeBlockComment: true, afterBlockComment: true, beforeLineComment: true, afterLineComment: false, allowBlockStart: true, allowObjectStart: true, applyDefaultIgnorePatterns: false, ignorePattern: "pragma", afterHashbangComment: true }] } } ];该规则的报告消息只有两种(源码 messages 定义):Expected line before comment.(注释前缺少空行)与Expected line after comment.(注释后缺少空行)。
beforeBlockComment:块注释前必须空行
默认{ "beforeBlockComment": true }下,不正确的代码——块注释紧跟在上一条语句之后:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true }]*/ var night = "long"; /* what a great and wonderful day */ var day = "great"正确的代码——块注释前保留一个空行:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true }]*/ var night = "long"; /* what a great and wonderful day */ var day = "great"afterBlockComment:块注释后必须空行
启用{ "afterBlockComment": true }后,不正确的代码——块注释之后直接跟代码:
/*eslint lines-around-comment: ["error", { "afterBlockComment": true }]*/ var night = "long"; /* what a great and wonderful day */ var day = "great"正确的代码——块注释前后都有空行:
/*eslint lines-around-comment: ["error", { "afterBlockComment": true }]*/ var night = "long"; /* what a great and wonderful day */ var day = "great"beforeLineComment:行注释前必须空行
启用{ "beforeLineComment": true }后,不正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true }]*/ var night = "long"; // what a great and wonderful day var day = "great"正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true }]*/ var night = "long"; // what a great and wonderful day var day = "great"afterLineComment:行注释后必须空行
启用{ "afterLineComment": true }后,不正确的代码:
/*eslint lines-around-comment: ["error", { "afterLineComment": true }]*/ var night = "long"; // what a great and wonderful day var day = "great"正确的代码:
/*eslint lines-around-comment: ["error", { "afterLineComment": true }]*/ var night = "long"; // what a great and wonderful day var day = "great"allowBlockStart / allowBlockEnd:块结构边界的注释放行
当同时启用beforeLineComment/afterLineComment或beforeBlockComment/afterBlockComment时,处于"块的开头或结尾"的注释往往不需要额外的空行(空行已由花括号分隔)。allowBlockStart与allowBlockEnd正是为此设计,覆盖的函数体、if块、class体、switch语句与类静态块(static { })。
{ "beforeLineComment": true, "allowBlockStart": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowBlockStart": true }]*/ function foo(){ // what a great and wonderful day var day = "great" return day; } if (bar) { // what a great and wonderful day foo(); } class C { // what a great and wonderful day method() { // what a great and wonderful day foo(); } static { // what a great and wonderful day foo(); } }{ "beforeBlockComment": true, "allowBlockStart": true }下正确的代码(含switch):
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowBlockStart": true }]*/ function foo(){ /* what a great and wonderful day */ var day = "great" return day; } if (bar) { /* what a great and wonderful day */ foo(); } class C { /* what a great and wonderful day */ method() { /* what a great and wonderful day */ foo(); } static { /* what a great and wonderful day */ foo(); } } switch (foo) { /* what a great and wonderful day */ case 1: bar(); break; }{ "afterLineComment": true, "allowBlockEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowBlockEnd": true }]*/ function foo(){ var day = "great" return day; // what a great and wonderful day } if (bar) { foo(); // what a great and wonderful day } class C { method() { foo(); // what a great and wonderful day } static { foo(); // what a great and wonderful day } // what a great and wonderful day }{ "afterBlockComment": true, "allowBlockEnd": true }下正确的代码(含switch):
/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowBlockEnd": true }]*/ function foo(){ var day = "great" return day; /* what a great and wonderful day */ } if (bar) { foo(); /* what a great and wonderful day */ } class C { method() { foo(); /* what a great and wonderful day */ } static { foo(); /* what a great and wonderful day */ } /* what a great and wonderful day */ } switch (foo) { case 1: bar(); break; /* what a great and wonderful day */ }源码:块边界的判定方式
allowBlockStart/allowBlockEnd的判定由 isCommentAtBlockStart 与 isCommentAtBlockEnd 完成,二者分别委托isCommentAtParentStart/isCommentAtParentEnd,覆盖ClassBody、BlockStatement、StaticBlock、SwitchCase、SwitchStatement五种父节点类型:
- 块起始:注释所在行与父节点"起始 token"所在行相差 1(
token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1)才判定为块开头; - 块结尾:父节点结束行与注释结束行相差 1(
parent.loc.end.line - token.loc.end.line === 1)才判定为块结尾。
对于StaticBlock与SwitchStatement有特殊处理:getParentNodeOfToken 中,StaticBlock只把花括号内的注释视为静态块的一部分(static\n// comment\n{ }中花括号外的注释返回null);SwitchStatement的起始 token 取判别表达式之后的左花括号,因此注释必须出现在switch (x) {的左花括号之后才算块开头。测试用例 tests/lib/rules/lines-around-comment.js#L2776-L2814 专门验证了"位于switch判别式括号内的注释不会被allowBlockStart放行"这一边界行为。
allowClassStart / allowClassEnd:类边界的精细控制
allowClassStart与allowClassEnd单独控制类体(ClassBody)的起始与结尾,且优先级高于块开关:当显式设为false时,会覆盖allowBlockStart/allowBlockEnd对类体的放行(见源码 checkForEmptyLine 中blockStartAllowed/blockEndAllowed的组合条件)。
{ "beforeLineComment": true, "allowClassStart": false }下不正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": false }]*/ class foo { // what a great and wonderful day day() {} };{ "beforeLineComment": true, "allowClassStart": false }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": false }]*/ class foo { // what a great and wonderful day day() {} };{ "beforeLineComment": true, "allowClassStart": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": true }]*/ class foo { // what a great and wonderful day day() {} };同样,块注释下{ "beforeBlockComment": true, "allowClassStart": false }不正确:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": false }]*/ class foo { /* what a great and wonderful day */ day() {} };{ "beforeBlockComment": true, "allowClassStart": false }正确:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": false }]*/ class foo { /* what a great and wonderful day */ day() {} };{ "beforeBlockComment": true, "allowClassStart": true }正确:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": true }]*/ class foo { /* what a great and wonderful day */ day() {} };{ "afterLineComment": true, "allowClassEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowClassEnd": true }]*/ class foo { day() {} // what a great and wonderful day };{ "afterBlockComment": true, "allowClassEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowClassEnd": true }]*/ class foo { day() {} /* what a great and wonderful day */ };allowObjectStart / allowObjectEnd:对象字面量边界
对象相关的放行同时覆盖对象字面量ObjectExpression与对象解构模式ObjectPattern(见 isCommentAtObjectStart 与 isCommentAtObjectEnd),因此对象解构const { ... } = ...中的注释同样受控。
{ "beforeLineComment": true, "allowObjectStart": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowObjectStart": true }]*/ var foo = { // what a great and wonderful day day: "great" }; const { // what a great and wonderful day foo: someDay } = {foo: "great"}; const { // what a great and wonderful day day } = {day: "great"};{ "beforeBlockComment": true, "allowObjectStart": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowObjectStart": true }]*/ var foo = { /* what a great and wonderful day */ day: "great" }; const { /* what a great and wonderful day */ foo: someDay } = {foo: "great"}; const { /* what a great and wonderful day */ day } = {day: "great"};{ "afterLineComment": true, "allowObjectEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowObjectEnd": true }]*/ var foo = { day: "great" // what a great and wonderful day }; const { foo: someDay // what a great and wonderful day } = {foo: "great"}; const { day // what a great and wonderful day } = {day: "great"};{ "afterBlockComment": true, "allowObjectEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowObjectEnd": true }]*/ var foo = { day: "great" /* what a great and wonderful day */ }; const { foo: someDay /* what a great and wonderful day */ } = {foo: "great"}; const { day /* what a great and wonderful day */ } = {day: "great"};allowArrayStart / allowArrayEnd:数组字面量边界
数组相关放行同时覆盖数组字面量ArrayExpression与数组解构模式ArrayPattern(见 isCommentAtArrayStart 与 isCommentAtArrayEnd)。
{ "beforeLineComment": true, "allowArrayStart": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowArrayStart": true }]*/ var day = [ // what a great and wonderful day "great", "wonderful" ]; const [ // what a great and wonderful day someDay ] = ["great", "not great"];{ "beforeBlockComment": true, "allowArrayStart": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowArrayStart": true }]*/ var day = [ /* what a great and wonderful day */ "great", "wonderful" ]; const [ /* what a great and wonderful day */ someDay ] = ["great", "not great"];{ "afterLineComment": true, "allowArrayEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowArrayEnd": true }]*/ var day = [ "great", "wonderful" // what a great and wonderful day ]; const [ someDay // what a great and wonderful day ] = ["great", "not great"];{ "afterBlockComment": true, "allowArrayEnd": true }下正确的代码:
/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowArrayEnd": true }]*/ var day = [ "great", "wonderful" /* what a great and wonderful day */ ]; const [ someDay /* what a great and wonderful day */ ] = ["great", "not great"];ignorePattern:自定义忽略模式
默认情况下,规则会忽略以eslint、jshint、jslint、istanbul、global、exported、jscs开头的注释。这些关键词的底层正则定义在 lib/rules/utils/ast-utils.js#L45-L46 的COMMENTS_IGNORE_PATTERN:
const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;注意其中eslint与jscs后不带空白限定(\s+),即注释内容以这两个词开头即被忽略;而jshint、jslint、istanbul、global(s)、exported后要求紧跟空白。因此默认配置下,以下代码全部正确(测试见 tests/lib/rules/lines-around-comment.js#L1196-L1203):
foo(); /* jshint mentioned in this comment */ bar();而/* fallthrough */这类不在默认模式中的注释不会被忽略,仍会触发报告(测试 tests/lib/rules/lines-around-comment.js#L2765-L2775)。
如需在默认模式之外额外忽略更多注释,可将ignorePattern设置为一个字符串模式,该字符串会被直接传入RegExp构造函数(new RegExp(ignorePattern, "u"),见源码 lib/rules/lines-around-comment.js#L160),因此可以使用正则语法并默认启用u(Unicode)标志。
{ "ignorePattern": "pragma" }下正确的代码:
/*eslint lines-around-comment: ["error", { "ignorePattern": "pragma" }] */ foo(); /* jshint mentioned in this comment */ bar(); foo(); /* a valid comment using pragma in it */{ "ignorePattern": "pragma" }下不正确的代码:
/*eslint lines-around-comment: ["error", { "ignorePattern": "pragma" }] */ 1 + 1; /* something else */源码 checkForEmptyLine 的匹配顺序是:先测applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value),再测ignorePattern && customIgnoreRegExp.test(token.value),二者任一命中即直接返回、不报告。
applyDefaultIgnorePatterns:关闭默认忽略模式
默认忽略模式始终生效,即使你同时提供了ignorePattern。如果希望完全放弃默认模式、只保留自定义模式,请将applyDefaultIgnorePatterns设为false。源码实现见 lib/rules/lines-around-comment.js#L161-L162:options.applyDefaultIgnorePatterns !== false,即只有显式传false才会关闭。
{ "ignorePattern": "pragma", applyDefaultIgnorePatterns: false }下正确的代码:
/*eslint lines-around-comment: ["error", { "ignorePattern": "pragma", applyDefaultIgnorePatterns: false }] */ foo(); /* a valid comment using pragma in it */{ "applyDefaultIgnorePatterns": false }下不正确的代码——默认模式被关闭后,jshint注释不再被忽略:
/*eslint lines-around-comment: ["error", { "applyDefaultIgnorePatterns": false }] */ foo(); /* jshint mentioned in comment */测试 tests/lib/rules/lines-around-comment.js#L2664-L2762 逐一验证了关闭默认模式后eslint、jshint、jslint、istanbul、global、globals、exported、jscs全部不再豁免,均会报告before错误。
afterHashbangComment:hashbang 注释后的空行
#!开头的 hashbang 注释(shebang)在 Node.js 可执行脚本中用于指定解释器。启用afterHashbangComment: true后,规则要求 hashbang 注释之后必须紧跟一个空行。源码中 hashbang 只检查after方向、before恒为false(lib/rules/lines-around-comment.js#L569-L576)。
不正确的代码:
#!foo var day = "great" /*eslint lines-around-comment: ["error", { "afterHashbangComment": true }] */正确的代码:
#!foo var day = "great" /*eslint lines-around-comment: ["error", { "afterHashbangComment": true }] */测试用例 tests/lib/rules/lines-around-comment.js#L2816-L2826 验证了#!foo\nvar a = 1;会报告after错误并自动修复为#!foo\n\nvar a = 1;。
源码实现:规则的核心判定流程
理解规则内部逻辑有助于预测各种边界输入的行为,其核心流程集中在 checkForEmptyLine:
- 忽略检查:先检查默认忽略模式与自定义
ignorePattern,命中即跳过; - 文件边界豁免:
prevLineNum < 1(注释在首行)时取消before检查,nextLineNum >= numLines(注释在末行)时取消after检查; - 行内注释豁免:通过 codeAroundComment 向前后查找非注释 token,若任一 token 与注释在同一行(借助
astUtils.isTokenOnSameLine),则视为行内注释直接跳过——这就是文档中"不检查与代码同行注释"的底层实现; - 异常放行组合:将
blockStartAllowed、classStartAllowed、objectStartAllowed、arrayStartAllowed合并为exceptionStartAllowed(end同理),任一命中即跳过对应的before/after检查;其中allowClassStart: false/allowClassEnd: false会否决allowBlockStart/allowBlockEnd对类体的放行; - 空行判定:基于
sourceCode.lines与注释行号构建commentAndEmptyLines集合(见 getEmptyLineNums 与 getCommentLineNums),用注释上一行/下一行的行号是否在该集合中判断是否已有空行或相邻注释; - 自动修复:规则声明
fixable: "whitespace"(lib/rules/lines-around-comment.js#L88),缺少前空行时在注释行首插入\n(fixer.insertTextBeforeRange),缺少后空行时在注释 token 后插入\n(fixer.insertTextAfter),因此eslint --fix可以自动补齐空行。
内联注释与连续注释的处理
- 行尾内联注释:
foo() // An inline comment不会被检查。测试 tests/lib/rules/lines-around-comment.js#L70-L78 中,即使同时开启afterLineComment与beforeLineComment,行内注释也不会触发错误; - 连续的注释行:相邻注释彼此之间不要求空行。测试 tests/lib/rules/lines-around-comment.js#L44-L46 显示
// line\n// line两行连排时,同时开启前后空行检查依然合法——因为注释行号本身加入了commentAndEmptyLines集合,且源码 lib/rules/lines-around-comment.js#L505-L509 对"前一个 token 是同行的注释"也做了豁免。
与相关规则的配合
lines-around-comment与两个相邻的格式规则共同构成注释/块排版体系:
- space-before-blocks:要求块(
{)前有空格,控制的是块与语句之间的空白; - spaced-comment:要求注释内容以空格开始(如
// comment而非//comment),控制的是注释符号内部的格式。
三者分别管住"块前空格、注释符号内空格、注释周围空行",通常一起启用以形成统一的注释排版风格。lines-around-comment的规则类型为layout(布局类),在 规则元信息 中标明recommended: false,即不随eslint:recommended自动启用,需要团队按需显式配置。
与 JSX/注释指令的注意事项
规则基于sourceCode.getAllComments()遍历所有注释 token,因此同样会处理 JSX 中的注释。另外,eslint开头的注释(如/* eslint-disable */)默认被COMMENTS_IGNORE_PATTERN忽略,不会因缺少空行被报告——这保证了关闭/启用规则的指令注释不会反过来触发本规则的错误。
When Not To Use It:何时关闭该规则
很多开发者偏爱紧凑的代码风格,并不介意注释紧贴着代码出现。如果你属于这类风格,或者你的代码库普遍使用行内注释、/* jshint ... */这类工具注释,并且不希望因此引入大量空行,那么本规则并不适合你——可以直接关闭("lines-around-comment": "off"),改用团队统一的注释规范或交给格式化工具处理。
补充说明:规则的维护状态
从源码 meta.deprecated 可以看到,本规则自ESLint v8.53.0起被标记为弃用(deprecated):ESLint 官方决定将格式化类规则移出核心库,计划保留至 v11.0.0;后续维护由@stylistic/eslint-plugin中的同名lines-around-comment规则接管(迁移指南见 ESLint Stylistic 项目)。因此新项目若需要该规则,建议直接使用 Stylistic 插件的对应规则;在使用当前 ESLint 版本(v11 之前)时,本规则依然可用,但要注意它不会再有新功能演进。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考