ESLint indent 规则完全指南:一致缩进校验的配置、选项与源码原理
2026/9/12 1:34:06 网站建设 项目流程

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 个缩进单位;switchcase分支默认与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 的注释清晰描述了整体策略,可归纳为四步:

  1. 用一个OffsetStorage实例存储“期望偏移量”映射:每个 token 相对另一个指定 token(或相对文件首列)有一个期望偏移。
  2. 遍历 AST 时按需修改 token 的期望偏移。例如进入BlockStatement时,把块内所有 token 相对左花括号整体偏移 1 个缩进级别。
  3. AST 遍历完成后,根据OffsetStorage计算每个 token 的期望缩进。
  4. 逐行比较该行第一个 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 = 1indentType = "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 将跳过缩进检查,作为与规则意见不一致时的“逃生舱”
SwitchCase0switch语句中case子句相对switch的缩进级别
VariableDeclarator1var声明符的缩进级别;可为数字、"first",或{var, let, const}对象分别指定
outerIIFEBody1文件级 IIFE(立即执行函数表达式)函数体的缩进;可为"off"关闭检查
MemberExpression1多行属性链的缩进;可为"off"关闭检查
FunctionDeclaration{parameters: 1, body: 1}函数声明的parametersbody缩进;parameters可为数字或"first",也可"off"
FunctionExpression{parameters: 1, body: 1}函数表达式的parametersbody缩进;parameters同上
StaticBlock{body: 1}类静态块(static {})的函数体缩进
CallExpression{arguments: 1}调用表达式的参数缩进;arguments可为数字或"first",也可"off"
ArrayExpression1数组元素缩进;可为"first""off"
ObjectExpression1对象属性缩进;可为"first""off"
ImportDeclaration1import 语句缩进;可为"first""off"
flatTernaryExpressionsfalsetrue时,嵌套在其他三元表达式中的三元表达式无需缩进
offsetTernaryExpressionsfalsetrue时,三元表达式的值需要缩进
ignoreCommentsfalsetrue时,允许注释不与其上一行/下一行的节点对齐

说明:"first"表示列表中的所有元素与第一个元素对齐;"off"表示完全跳过该类节点的缩进检查。schema 中ELEMENT_LIST_SCHEMA(lib/rules/indent.js)统一约束了integer (>=0)"first""off"三种取值。

缩进级别(Level)的计算方式

“级别”是缩进单位的倍数。以下示例展示了基础缩进量在不同选项下的叠加效果:

  • 基础缩进 4 空格、VariableDeclarator2:多行变量声明缩进 8 空格。
  • 基础缩进 2 空格、VariableDeclarator2:多行变量声明缩进 4 空格。
  • 基础缩进 2 空格、VariableDeclarator{"var": 2, "let": 2, "const": 3}varlet声明缩进 4 空格,const声明缩进 6 空格。
  • 基础缩进 Tab、VariableDeclarator2:多行变量声明缩进 2 个 Tab。
  • 基础缩进 2 空格、SwitchCase0caseswitch对齐不缩进。
  • 基础缩进 2 空格、SwitchCase1case相对switch缩进 2 空格。
  • 基础缩进 2 空格、SwitchCase2case相对switch缩进 4 空格。
  • 基础缩进 Tab、SwitchCase2case相对switch缩进 2 个 Tab。
  • 基础缩进 2 空格、MemberExpression0:多行属性链缩进 0 空格。
  • 基础缩进 2 空格、MemberExpression1:多行属性链缩进 2 空格。
  • 基础缩进 2 空格、MemberExpression2:多行属性链缩进 4 空格。
  • 基础缩进 4 空格、MemberExpression0:多行属性链缩进 0 空格。
  • 基础缩进 4 空格、MemberExpression1:多行属性链缩进 4 空格。
  • 基础缩进 4 空格、MemberExpression2:多行属性链缩进 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-pluginindent规则来承担缩进校验职责;在存量 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),仅供参考

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

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

立即咨询