ESLint indent 规则完全指南:一致缩进校验的配置、选项与源码原理
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
本文以 ESLint 核心规则indent(强制一致的缩进风格)为主题,系统讲解其默认行为、全部配置选项、正确与错误示例,并结合仓库内 lib/rules/indent.js 的源码实现,揭示该规则如何通过偏移存储(OffsetStorage)与期望缩进计算来逐行校验缩进。读完本文,你将能精确配置 2 空格、4 空格、Tab 及各种节点级别的缩进策略,并理解indent规则的底层工作机制与弃用状态。
规则背景:为什么需要强制缩进
不同的风格指南对嵌套代码块与语句的缩进有明确要求,例如:
function hello(indentSize, type) { if (indentSize === 4 && type !== 'tab') { console.log('Each next indentation will increase on 4 spaces'); } }社区主流风格指南的推荐各不相同:
- 两个空格(不长、不用 Tab):Google、npm、Node.js、Idiomatic、Felix
- Tab:jQuery
- 四个空格:Crockford
indent规则正是用来在项目中强制执行其中某一种统一风格,避免团队代码缩进混乱。
规则行为与默认配置
indent规则强制一致的缩进风格,默认风格为 4 空格(rule_type: layout,见 docs/src/rules/indent.md 文档头部与 docs/src/_data/rules_meta.json 中的元数据)。
从 lib/rules/indent.js 的create函数可以看到默认值定义:
let indentType = "space"; let indentSize = 4; const options = { SwitchCase: 0, VariableDeclarator: { var: 1, let: 1, const: 1 }, outerIIFEBody: 1, FunctionDeclaration: { parameters: 1, body: 1 }, FunctionExpression: { parameters: 1, body: 1 }, StaticBlock: { body: 1 }, CallExpression: { arguments: 1 }, MemberExpression: 1, ArrayExpression: 1, ObjectExpression: 1, ImportDeclaration: 1, flatTernaryExpressions: false, ignoredNodes: [], ignoreComments: false, };即:每个嵌套层级默认增加 1 个缩进单位;switch的case分支默认与switch对齐(SwitchCase: 0),其余列表类节点(数组、对象、函数参数、导入成员等)默认缩进 1 级。
默认选项下的错误示例
/*eslint indent: "error"*/ if (a) { b=c; function foo(d) { e=f; } }默认选项下的正确示例
/*eslint indent: "error"*/ if (a) { b=c; function foo(d) { e=f; } }源码原理:indent 规则如何工作
lib/rules/indent.js 的注释清晰描述了整体策略,可归纳为四步:
- 用一个
OffsetStorage实例存储“期望偏移量”映射:每个 token 相对另一个指定 token(或相对文件首列)有一个期望偏移。 - 遍历 AST 时按需修改 token 的期望偏移。例如进入
BlockStatement时,把块内所有 token 相对左花括号整体偏移 1 个缩进级别。 - AST 遍历完成后,根据
OffsetStorage计算每个 token 的期望缩进。 - 逐行比较该行第一个 token 的期望缩进与实际缩进,不一致即报错。
支撑这一流程的三个核心辅助类(均定义在 lib/rules/indent.js 中):
IndexMap(L130-L177):以 token 范围为 key 的可变映射,支持按区间插入、删除偏移描述符,并将数组按最大 key 预分配以避免动态扩容的性能损耗。TokenInfo(L182-L240):基于sourceCode.tokensAndComments构建“每行首个 token”映射,并提供获取 token 实际缩进字符串的能力。OffsetStorage(L245-L494):核心偏移容器,负责setDesiredOffset(s)设置期望偏移、getDesiredIndent计算期望缩进、matchOffsetOf实现"first"对齐模式、ignoreToken支持被忽略的 token。
其中setDesiredOffsets(L373-L419)有一个重要的**同行折叠(collapsing)**行为:若两个 token 位于同一行,则它们之间的层级偏移会被折叠为 0。例如:
( [ bar ] )bar需要相对[偏移 1 级(4 空格),而[又相对(偏移 1 级;但由于(与[同处一行,偏移被折叠,bar最终只缩进 4 空格而不是 8 空格。这一机制让规则作者只需对所有 token 统一设置偏移,而无需关心 token 所在行,从而显著简化各节点监听器(listener)的实现。
规则的报错信息在 lib/rules/indent.js 中定义为:
Expected indentation of {{expected}} but found {{actual}}.消息组装逻辑见createErrorMessageData(L751-L782),例如“期望 4 空格但发现 2”(Expected 4 spaces but found 2),或 Tab 模式下“期望 2 tabs”。
基本配置:数字与 "tab" 两种模式
该规则接受一个混合型配置,第一项既可以是表示空格数量的整数,也可以是字符串"tab"。
2 空格缩进:
{ "indent": ["error", 2] }Tab 缩进:
{ "indent": ["error", "tab"] }在 lib/rules/indent.js 中可以看到解析逻辑:当第一个参数为"tab"时indentSize = 1、indentType = "tab"(缩进单位是\t);否则indentSize取整数值、indentType = "space"(缩进单位是" ")。schema 规定该整数minimum: 0(L549-L550)。
"tab" 模式错误示例
/*eslint indent: ["error", "tab"]*/ if (a) { b=c; function foo(d) { e=f; } }"tab" 模式正确示例
/*eslint indent: ["error", "tab"]*/ if (a) { b=c; function foo(d) { e=f; } }对象选项完整参考
indent规则的第二项参数是一个对象,包含以下可配置项(schema 定义见 lib/rules/indent.js):
| 选项 | 默认值 | 作用 |
|---|---|---|
ignoredNodes | [] | 数组形式,传入 ESLint 选择器,匹配到的 AST 节点的直接子 token 将跳过缩进检查,作为与规则意见不一致时的“逃生舱” |
SwitchCase | 0 | switch语句中case子句相对switch的缩进级别 |
VariableDeclarator | 1 | var声明符的缩进级别;可为数字、"first",或{var, let, const}对象分别指定 |
outerIIFEBody | 1 | 文件级 IIFE(立即执行函数表达式)函数体的缩进;可为"off"关闭检查 |
MemberExpression | 1 | 多行属性链的缩进;可为"off"关闭检查 |
FunctionDeclaration | {parameters: 1, body: 1} | 函数声明的parameters与body缩进;parameters可为数字或"first",也可"off" |
FunctionExpression | {parameters: 1, body: 1} | 函数表达式的parameters与body缩进;parameters同上 |
StaticBlock | {body: 1} | 类静态块(static {})的函数体缩进 |
CallExpression | {arguments: 1} | 调用表达式的参数缩进;arguments可为数字或"first",也可"off" |
ArrayExpression | 1 | 数组元素缩进;可为"first"或"off" |
ObjectExpression | 1 | 对象属性缩进;可为"first"或"off" |
ImportDeclaration | 1 | import 语句缩进;可为"first"或"off" |
flatTernaryExpressions | false | 为true时,嵌套在其他三元表达式中的三元表达式无需缩进 |
offsetTernaryExpressions | false | 为true时,三元表达式的值需要缩进 |
ignoreComments | false | 为true时,允许注释不与其上一行/下一行的节点对齐 |
说明:"first"表示列表中的所有元素与第一个元素对齐;"off"表示完全跳过该类节点的缩进检查。schema 中ELEMENT_LIST_SCHEMA(lib/rules/indent.js)统一约束了integer (>=0)、"first"、"off"三种取值。
缩进级别(Level)的计算方式
“级别”是缩进单位的倍数。以下示例展示了基础缩进量在不同选项下的叠加效果:
- 基础缩进 4 空格、
VariableDeclarator为2:多行变量声明缩进 8 空格。 - 基础缩进 2 空格、
VariableDeclarator为2:多行变量声明缩进 4 空格。 - 基础缩进 2 空格、
VariableDeclarator为{"var": 2, "let": 2, "const": 3}:var与let声明缩进 4 空格,const声明缩进 6 空格。 - 基础缩进 Tab、
VariableDeclarator为2:多行变量声明缩进 2 个 Tab。 - 基础缩进 2 空格、
SwitchCase为0:case与switch对齐不缩进。 - 基础缩进 2 空格、
SwitchCase为1:case相对switch缩进 2 空格。 - 基础缩进 2 空格、
SwitchCase为2:case相对switch缩进 4 空格。 - 基础缩进 Tab、
SwitchCase为2:case相对switch缩进 2 个 Tab。 - 基础缩进 2 空格、
MemberExpression为0:多行属性链缩进 0 空格。 - 基础缩进 2 空格、
MemberExpression为1:多行属性链缩进 2 空格。 - 基础缩进 2 空格、
MemberExpression为2:多行属性链缩进 4 空格。 - 基础缩进 4 空格、
MemberExpression为0:多行属性链缩进 0 空格。 - 基础缩进 4 空格、
MemberExpression为1:多行属性链缩进 4 空格。 - 基础缩进 4 空格、
MemberExpression为2:多行属性链缩进 8 空格。
各对象选项详解与示例
ignoredNodes
通过 ESLint 选择器精确跳过某些 AST 节点内部子 token 的缩进检查(选择器语法可参考 selectors 文档,AST 节点类型基于 ESTree 规范,可用 espree 解析器配合 AST Explorer 查看代码片段的 AST)。
以下配置忽略ConditionalExpression(三元表达式)节点的缩进检查:
/*eslint indent: ["error", 4, { "ignoredNodes": ["ConditionalExpression"] }]*/ var a = foo ? bar : baz; var a = foo ? bar : baz;以下配置忽略 IIFE 函数体内的缩进:
/*eslint indent: ["error", 4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }]*/ (function() { foo(); bar(); })();SwitchCase
2, { "SwitchCase": 1 }的错误示例:
/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ switch(a){ case "a": break; case "b": break; }2, { "SwitchCase": 1 }的正确示例:
/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ switch(a){ case "a": break; case "b": break; }VariableDeclarator
2, { "VariableDeclarator": 1 }的错误示例:
/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ var a, b, c; let d, e, f; const g = 1, h = 2, i = 3;2, { "VariableDeclarator": 1 }的正确示例:
/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ var a, b, c; let d, e, f; const g = 1, h = 2, i = 3;2, { "VariableDeclarator": 2 }的正确示例(每级缩进 2 空格、声明符缩进 2 级 = 4 空格):
/*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ var a, b, c; let d, e, f; const g = 1, h = 2, i = 3;2, { "VariableDeclarator": "first" }的错误示例(未与第一个声明符对齐):
/*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ var a, b, c; let d, e, f; const g = 1, h = 2, i = 3;2, { "VariableDeclarator": "first" }的正确示例(全部与第一个声明符a对齐):
/*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ var a, b, c; let d, e, f; const g = 1, h = 2, i = 3;2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }的正确示例(var/let缩进 4 空格、const缩进 6 空格):
/*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ var a, b, c; let d, e, f; const g = 1, h = 2, i = 3;outerIIFEBody
2, { "outerIIFEBody": 0 }的错误示例(文件级 IIFE 内部被错误缩进,而外层if已正确缩进):
/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ (function() { function foo(x) { return x + 1; } })(); if (y) { console.log('foo'); }2, { "outerIIFEBody": 0 }的正确示例:
/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ (function() { function foo(x) { return x + 1; } })(); if (y) { console.log('foo'); }2, { "outerIIFEBody": "off" }的正确示例(关闭文件级 IIFE 检查后,两种风格都合法):
/*eslint indent: ["error", 2, { "outerIIFEBody": "off" }]*/ (function() { function foo(x) { return x + 1; } })(); (function() { function foo(x) { return x + 1; } })(); if (y) { console.log('foo'); }MemberExpression
2, { "MemberExpression": 1 }的错误示例:
/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ foo .bar .baz()2, { "MemberExpression": 1 }的正确示例:
/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ foo .bar .baz();FunctionDeclaration
2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }的错误示例(参数缩进应为 2 级即 4 空格,函数体应为 1 级即 2 空格):
/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ function foo(bar, baz, qux) { qux(); }同配置的正确示例:
/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ function foo(bar, baz, qux) { qux(); }2, { "FunctionDeclaration": {"parameters": "first"} }的错误示例:
/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ function foo(bar, baz, qux, boop) { qux(); }同配置的正确示例(后续参数与首个参数bar对齐):
/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ function foo(bar, baz, qux, boop) { qux(); }FunctionExpression
2, { "FunctionExpression": {"body": 1, "parameters": 2} }的错误示例:
/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ var foo = function(bar, baz, qux) { qux(); }同配置的正确示例:
/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ var foo = function(bar, baz, qux) { qux(); }2, { "FunctionExpression": {"parameters": "first"} }的错误示例:
/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ var foo = function(bar, baz, qux, boop) { qux(); }同配置的正确示例:
/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ var foo = function(bar, baz, qux, boop) { qux(); }StaticBlock
2, { "StaticBlock": {"body": 1} }的错误示例:
/*eslint indent: ["error", 2, { "StaticBlock": {"body": 1} }]*/ class C { static { foo(); } }同配置的正确示例:
/*eslint indent: ["error", 2, { "StaticBlock": {"body": 1} }]*/ class C { static { foo(); } }2, { "StaticBlock": {"body": 2} }的错误示例(期望缩进 2 级即 4 空格):
/*eslint indent: ["error", 2, { "StaticBlock": {"body": 2} }]*/ class C { static { foo(); } }同配置的正确示例:
/*eslint indent: ["error", 2, { "StaticBlock": {"body": 2} }]*/ class C { static { foo(); } }CallExpression
2, { "CallExpression": {"arguments": 1} }的错误示例:
/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ foo(bar, baz, qux );同配置的正确示例(每个参数相对调用行缩进 1 级 = 2 空格):
/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ foo(bar, baz, qux );2, { "CallExpression": {"arguments": "first"} }的错误示例:
/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ foo(bar, baz, baz, boop, beep);同配置的正确示例(后续参数与第一个参数bar对齐):
/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ foo(bar, baz, baz, boop, beep);ArrayExpression
2, { "ArrayExpression": 1 }的错误示例:
/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ var foo = [ bar, baz, qux ];同配置的正确示例:
/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ var foo = [ bar, baz, qux ];2, { "ArrayExpression": "first" }的错误示例:
/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ var foo = [bar, baz, qux ];同配置的正确示例(元素与第一个元素bar对齐):
/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ var foo = [bar, baz, qux ];ObjectExpression
2, { "ObjectExpression": 1 }的错误示例:
/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ var foo = { bar: 1, baz: 2, qux: 3 };同配置的正确示例:
/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ var foo = { bar: 1, baz: 2, qux: 3 };2, { "ObjectExpression": "first" }的错误示例:
/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ var foo = { bar: 1, baz: 2 };同配置的正确示例(属性与第一个属性bar对齐):
/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ var foo = { bar: 1, baz: 2 };ImportDeclaration
4, { "ImportDeclaration": 1 }(默认值)的正确示例,两种换行风格均合法:
/*eslint indent: ["error", 4, { "ImportDeclaration": 1 }]*/ import { foo, bar, baz, } from 'qux';/*eslint indent: ["error", 4, { "ImportDeclaration": 1 }]*/ import { foo, bar, baz, } from 'qux';4, { "ImportDeclaration": "first" }的错误示例:
/*eslint indent: ["error", 4, { "ImportDeclaration": "first" }]*/ import { foo, bar, baz, } from 'qux';同配置的正确示例(成员与第一个成员foo对齐):
/*eslint indent: ["error", 4, { "ImportDeclaration": "first" }]*/ import { foo, bar, baz, } from 'qux';flatTernaryExpressions
默认4, { "flatTernaryExpressions": false }的错误示例(嵌套三元未逐级缩进):
/*eslint indent: ["error", 4, { "flatTernaryExpressions": false }]*/ var a = foo ? bar : baz ? qux : boop;默认配置的正确示例:
/*eslint indent: ["error", 4, { "flatTernaryExpressions": false }]*/ var a = foo ? bar : baz ? qux : boop;4, { "flatTernaryExpressions": true }的错误示例(开启后嵌套三元不再需要额外缩进,此时逐级缩进反而报错):
/*eslint indent: ["error", 4, { "flatTernaryExpressions": true }]*/ var a = foo ? bar : baz ? qux : boop;同配置的正确示例(所有嵌套三元保持同一缩进层级):
/*eslint indent: ["error", 4, { "flatTernaryExpressions": true }]*/ var a = foo ? bar : baz ? qux : boop;offsetTernaryExpressions
默认2, { "offsetTernaryExpressions": false }的错误示例(分支内的函数体被错误偏移):
/*eslint indent: ["error", 2, { "offsetTernaryExpressions": false }]*/ condition ? () => { return true } : () => { false }默认配置的正确示例:
/*eslint indent: ["error", 2, { "offsetTernaryExpressions": false }]*/ condition ? () => { return true } : condition2 ? () => { return true } : () => { return false }2, { "offsetTernaryExpressions": true }的错误示例:
/*eslint indent: ["error", 2, { "offsetTernaryExpressions": true }]*/ condition ? () => { return true } : condition2 ? () => { return true } : () => { return false }同配置的正确示例(分支值需要相对?/:再偏移 1 级):
/*eslint indent: ["error", 2, { "offsetTernaryExpressions": true }]*/ condition ? () => { return true } : condition2 ? () => { return true } : () => { return false }ignoreComments
4, { "ignoreComments": true }下的额外合法写法(允许注释故意取消缩进):
/*eslint indent: ["error", 4, { "ignoreComments": true }] */ if (foo) { doSomething(); // comment intentionally de-indented doSomethingElse(); }弃用状态与迁移说明
indent属于 ESLint 核心的“格式化规则”,目前已被标记为弃用。根据 lib/rules/indent.js 中的meta.deprecated元数据(在 docs/src/_data/rules_meta.json 中同步维护):
- 弃用版本:ESLint v8.53.0
- 可用截止:ESLint v11.0.0
- 原因:格式化规则正在被移出 ESLint 核心,交由 ESLint Stylistic 项目(
@stylistic/eslint-plugin)继续维护,对应的迁移规则名为indent。
因此在新项目中,更推荐直接使用@stylistic/eslint-plugin的indent规则来承担缩进校验职责;在存量 ESLint 项目中,仍可按本文所述配置核心indent规则。该规则同样具备--fix自动修复能力(fixable: "whitespace",见 lib/rules/indent.js),可自动修正缩进问题。
兼容性说明
indent规则与其他主流格式化工具的对应关系:JSHint 中为同名的indent选项,JSCS 中对应validateIndentation规则。
深入阅读
- 规则文档原文:docs/src/rules/indent.md
- 规则实现源码:lib/rules/indent.js(2332 行,含偏移存储与各 AST 节点监听器)
- 规则测试用例:tests/lib/rules/indent.js(14411 行,覆盖
valid/invalid大量场景,可用于验证任意配置组合的行为) - 规则元数据:docs/src/_data/rules_meta.json
- 选择器语法(
ignoredNodes依赖):docs/src/extend/selectors.md
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考