ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ESLint indent 规则完全指南:一致缩进校验的配置、选项与源码原理

ESLint indent 规则完全指南:一致缩进校验的配置、选项与源码原理 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); } }社区主流风格指南的推荐各不相同两个空格不长、不用 TabGoogle、npm、Node.js、Idiomatic、FelixTabjQuery四个空格Crockfordindent规则正是用来在项目中强制执行其中某一种统一风格避免团队代码缩进混乱。规则行为与默认配置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) { bc; function foo(d) { ef; } }默认选项下的正确示例/*eslint indent: error*/ if (a) { bc; function foo(d) { ef; } }源码原理indent 规则如何工作lib/rules/indent.js 的注释清晰描述了整体策略可归纳为四步用一个OffsetStorage实例存储“期望偏移量”映射每个 token 相对另一个指定 token或相对文件首列有一个期望偏移。遍历 AST 时按需修改 token 的期望偏移。例如进入BlockStatement时把块内所有 token 相对左花括号整体偏移 1 个缩进级别。AST 遍历完成后根据OffsetStorage计算每个 token 的期望缩进。逐行比较该行第一个 token 的期望缩进与实际缩进不一致即报错。支撑这一流程的三个核心辅助类均定义在 lib/rules/indent.js 中IndexMapL130-L177以 token 范围为 key 的可变映射支持按区间插入、删除偏移描述符并将数组按最大 key 预分配以避免动态扩容的性能损耗。TokenInfoL182-L240基于sourceCode.tokensAndComments构建“每行首个 token”映射并提供获取 token 实际缩进字符串的能力。OffsetStorageL245-L494核心偏移容器负责setDesiredOffset(s)设置期望偏移、getDesiredIndent计算期望缩进、matchOffsetOf实现first对齐模式、ignoreToken支持被忽略的 token。其中setDesiredOffsetsL373-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}}.消息组装逻辑见createErrorMessageDataL751-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: 0L549-L550。tab 模式错误示例/*eslint indent: [error, tab]*/ if (a) { bc; function foo(d) { ef; } }tab 模式正确示例/*eslint indent: [error, tab]*/ if (a) { bc; function foo(d) { ef; } }对象选项完整参考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}函数声明的parameters与body缩进parameters可为数字或first也可offFunctionExpression{parameters: 1, body: 1}函数表达式的parameters与body缩进parameters同上StaticBlock{body: 1}类静态块static {}的函数体缩进CallExpression{arguments: 1}调用表达式的参数缩进arguments可为数字或first也可offArrayExpression1数组元素缩进可为first或offObjectExpression1对象属性缩进可为first或offImportDeclaration1import 语句缩进可为first或offflatTernaryExpressionsfalse为true时嵌套在其他三元表达式中的三元表达式无需缩进offsetTernaryExpressionsfalse为true时三元表达式的值需要缩进ignoreCommentsfalse为true时允许注释不与其上一行/下一行的节点对齐说明first表示列表中的所有元素与第一个元素对齐off表示完全跳过该类节点的缩进检查。schema 中ELEMENT_LIST_SCHEMAlib/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为0case与switch对齐不缩进。基础缩进 2 空格、SwitchCase为1case相对switch缩进 2 空格。基础缩进 2 空格、SwitchCase为2case相对switch缩进 4 空格。基础缩进 Tab、SwitchCase为2case相对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(); })();SwitchCase2, { 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; }VariableDeclarator2, { 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;outerIIFEBody2, { 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); }MemberExpression2, { MemberExpression: 1 }的错误示例/*eslint indent: [error, 2, { MemberExpression: 1 }]*/ foo .bar .baz()2, { MemberExpression: 1 }的正确示例/*eslint indent: [error, 2, { MemberExpression: 1 }]*/ foo .bar .baz();FunctionDeclaration2, { 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(); }FunctionExpression2, { 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(); }StaticBlock2, { 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(); } }CallExpression2, { 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);ArrayExpression2, { 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 ];ObjectExpression2, { 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 };ImportDeclaration4, { 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 }ignoreComments4, { 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.js2332 行含偏移存储与各 AST 节点监听器规则测试用例tests/lib/rules/indent.js14411 行覆盖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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进