ESLint sort-keys 规则详解:强制对象属性名按字母序排列的完整配置指南
2026/9/12 9:35:03 网站建设 项目流程

ESLint sort-keys 规则详解:强制对象属性名按字母序排列的完整配置指南

【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint

本文以 ESLint 仓库中的 sort-keys 官方文档 为核心,结合 规则源码 与 单元测试 编写。sort-keys是 ESLint 内置的 suggestion(建议)类规则,用于检查对象字面量中属性定义的顺序,要求所有属性名按字母顺序排序。读完本文,你将掌握该规则的六大配置项(asc/desccaseSensitivenaturalminKeysallowLineSeparatedGroupsignoreComputedKeys)的精确语义与典型用法,理解计算属性、展开属性(Spread)等边界情况的处理规则,并能根据团队偏好自由定制排序策略。

规则简介与设计动机

在声明多个对象属性时,部分开发者倾向于将属性名按字母顺序排列,以便后续更容易查找属性、或进行代码 diff(合并冲突时按字母序排列的属性顺序冲突更少、更易定位);另一部分开发者则认为这增加了书写复杂度并成为维护负担。sort-keys规则正是为前一种偏好提供的自动化保障——它不再依赖人工自觉,而是通过 lint 在构建阶段强制属性顺序,同时提供多种配置以适配不同团队的代码风格。

该规则在仓库元数据中的定义位于 conf/rule-type-list.json(suggestion类型);相关规则还包括sort-imports(对 import 语句排序)与sort-vars(对变量声明排序),三者共同构成 ESLint 的"排序"规则族。规则源码 lib/rules/sort-keys.js 中标记recommended: falsefrozen: true,表示它不会出现在eslint:recommended预设中,且属于"冻结"规则(其行为与选项集合保持稳定,不随版本随意变动),需要使用时须在配置中显式开启。

Rule Details:规则检查什么

本规则检查所有对象表达式(Object Expression)中的属性定义,并验证所有属性名是否按字母顺序排列。它逐对比较相邻属性:一旦发现后一个属性名应排在前一个之前,就报告一个错误。

不正确的代码示例(默认配置,即升序):

/*eslint sort-keys: "error"*/ const obj1 = {a: 1, c: 3, b: 2}; const obj2 = {a: 1, "c": 3, b: 2}; // Case-sensitive by default(默认大小写敏感)。 const obj3 = {a: 1, b: 2, C: 3}; // Non-natural order by default(默认非自然序,"10" 按字典序排在 "2" 之前)。 const obj4 = {1: a, 2: c, 10: b}; // 本规则同样检查拥有"简单名"(Simple Name)的计算属性。 // 简单名指由 Identifier 节点或 Literal 节点表达的名称。 const S = Symbol("s") const obj5 = {a: 1, ["c"]: 3, b: 2}; const obj6 = {a: 1, [S]: 3, b: 2};

正确的代码示例

/*eslint sort-keys: "error"*/ const obj1 = {a: 1, b: 2, c: 3}; const obj2 = {a: 1, "b": 2, c: 3}; // Case-sensitive by default(大写字母 C 排在所有小写字母之前)。 const obj3 = {C: 3, a: 1, b: 2}; // Non-natural order by default("1" < "10" < "2" 为字典序)。 const obj4 = {1: a, 10: b, 2: c}; // 本规则同样检查拥有简单名的计算属性。 const obj5 = {a: 1, ["b"]: 2, c: 3}; const obj6 = {a: 1, [b]: 2, c: 3}; // 本规则忽略拥有非简单名的计算属性。 const obj7 = {a: 1, [c + d]: 3, b: 2}; const obj8 = {a: 1, ["c" + "d"]: 3, b: 2}; const obj9 = {a: 1, [`${c}`]: 3, b: 2}; const obj10 = {a: 1, [tag`c`]: 3, b: 2}; // 本规则不报告被展开属性(Spread)分隔开的未排序属性。 const obj11 = {b: 1, ...c, a: 2};

三类特殊属性节点:理解规则的三个"忽略/重置"边界

从上面的示例中可以提炼出三个关键边界,它们在 lib/rules/sort-keys.js 中有精确的源码实现:

  1. 非简单名的计算属性直接忽略。源码中getPropertyName(node)先调用astUtils.getStaticPropertyName(实现在 lib/rules/utils/ast-utils.js),若拿不到静态名则回退取node.key.name;两者都拿不到(如[c + d][`${c}`][tag`c`])则返回null。当thisName === null时规则直接return,不参与排序比较,也不影响前后属性的比较。
  2. 简单名的计算属性参与排序["c"][S](Symbol 变量)等可以被静态求值的键名照常参与字母序比较。
  3. 展开属性(Spread)重置排序基准。源码中的SpreadElement(node)处理器在父节点是ObjectExpression时将stack.prevName置为null,因此{b: 1, ...c, a: 2}ba不再被比较。测试文件 tests/lib/rules/sort-keys.js 中大量验证了这一行为,例如{a:1, ...z, b:1}{b:1, ...z, a:1}{...a, b:1, ...c, d:1}均为合法用例,而{...z, a:1, b:1}这类未被展开属性分隔的仍会被检查。

Options 配置项详解

规则完整配置格式如下:

{ "sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}] }

第 1 个选项为"asc""desc"

  • "asc"(默认)—— 强制属性按升序排列;
  • "desc"—— 强制属性按降序排列。

第 2 个选项是一个对象,包含以下属性:

配置项类型默认值含义
caseSensitivebooleantrue若为true,强制属性按大小写敏感的字典序排列;false则忽略大小写(统一转为小写后比较)
minKeysinteger(最小值为 2)2指定对象需要拥有的最小键数;键数少于该值的对象即使未排序也不会报错
naturalbooleanfalse若为true,按"自然序"排序(见下文);默认的字母序下数字按字典序排列
allowLineSeparatedGroupsbooleanfalse若为true,允许通过空行把对象键分成多个"组",空行会重置排序状态
ignoreComputedKeysbooleanfalse若为true,忽略所有计算键,且计算键会重置其后非计算键的排序

以上默认值与 schema 校验定义在 lib/rules/sort-keys.js 的defaultOptionsschema字段中(minKeysminimum: 2保证不会出现低于 2 的无意义配置;additionalProperties: false拒绝未知选项)。

自然序(natural)到底是什么意思

natural(自然序)是指以人类直觉的方式比较同时包含字母与数字的字符串:它基本按"数值"而非"字母表"排序。例如对键名1, 3, 6, 8, 10

  • natural: true时的顺序:1 → 3 → 6 → 8 → 10(数字按数值大小);
  • natural: false时(默认字典序):1 → 10 → 3 → 6 → 8"10"的首字符"1"小于"3",故排在前面)。

源码中该功能由natural-compare库实现(lib/rules/sort-keys.js第 13 行的require("natural-compare")),比较函数形如naturalCompare(a, b) <= 0

desc 选项示例

不正确的代码["error", "desc"]):

/*eslint sort-keys: ["error", "desc"]*/ const obj1 = {b: 2, c: 3, a: 1}; const obj2 = {"b": 2, c: 3, a: 1}; // Case-sensitive by default(大小写敏感:C 作为最大项应在最前)。 const obj3 = {C: 1, b: 3, a: 2}; // Non-natural order by default(字典序下 "10" 应排在 "2" 与 "1" 之前)。 const obj4 = {10: b, 2: c, 1: a};

正确的代码

/*eslint sort-keys: ["error", "desc"]*/ const obj1 = {c: 3, b: 2, a: 1}; const obj2 = {c: 3, "b": 2, a: 1}; // Case-sensitive by default。 const obj3 = {b: 3, a: 2, C: 1}; // Non-natural order by default。 const obj4 = {2: c, 10: b, 1: a};

caseSensitive 选项示例

不正确的代码["error", "asc", {caseSensitive: false}]):

/*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ const obj1 = {a: 1, c: 3, C: 4, b: 2}; const obj2 = {a: 1, C: 3, c: 4, b: 2};

正确的代码

/*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ const obj1 = {a: 1, b: 2, c: 3, C: 4}; const obj2 = {a: 1, b: 2, C: 3, c: 4};

caseSensitive: false时,源码会选择带I(insensitive)后缀的比较函数,例如ascI(a, b)内部执行a.toLowerCase() <= b.toLowerCase()。注意此时a < b < c < Ca < b < C < c两种排列都合法,因为忽略大小写后cC视为相等,谁前谁后规则不干预。

natural 选项示例

不正确的代码["error", "asc", {natural: true}]):

/*eslint sort-keys: ["error", "asc", {natural: true}]*/ const obj = {1: a, 10: c, 2: b};

正确的代码

/*eslint sort-keys: ["error", "asc", {natural: true}]*/ const obj = {1: a, 2: b, 10: c};

minKeys 选项示例

不正确的代码["error", "asc", {minKeys: 4}],对象键数达到 4 个或更多才会报错):

/*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ // 4 keys const obj1 = { b: 2, a: 1, // not sorted correctly (should be 1st key) c: 3, d: 4, }; // 5 keys const obj2 = { 2: 'a', 1: 'b', // not sorted correctly (should be 1st key) 3: 'c', 4: 'd', 5: 'e', };

正确的代码(键数不足 4 时不受检查):

/*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ // 3 keys const obj1 = { b: 2, a: 1, c: 3, }; // 2 keys const obj2 = { 2: 'b', 1: 'a', };

minKeys的默认值为2,意味着默认情况下所有含未排序键的对象都会产生 lint 错误;将其调大可避免对小型对象(如两三个键的配置对象)强制执行排序,减少对既有代码风格的干扰。源码中numKeys < minKeys的判断(lib/rules/sort-keys.js)直接使用了ObjectExpression节点的properties.length作为键数。

allowLineSeparatedGroups 选项示例

allowLineSeparatedGroups: true时,空行成为分组的边界:属性后的空行会重置排序状态,空行之后的新组重新开始排序。这对"按语义分组组织对象键"的写法非常友好。

不正确的代码["error", "asc", {allowLineSeparatedGroups: true}]):

/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ // 同一组内仍有未排序键(b、c、a 同组且无空行分隔)。 const obj1 = { b: 1, c () { }, a: 3 } // 第二组(z、y)内部未排序。 const obj2 = { b: 1, c: 2, z () { }, y: 3 } // 注释不构成分组边界,z 与 y 仍在同一组。 const obj3 = { b: 1, c: 2, z () { }, // comment y: 3, } // 逗号前的注释同样不能分隔组。 const obj4 = { b: 1 // comment before comma , a: 2 };

正确的代码

/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ // 空行将键分为 e/f/g 与 a/b/c 两组,组内各自有序。 const obj1 = { e: 1, f: 2, g: 3, a: 4, b: 5, c: 6 } // 空行后新组从 a 开始重新排序。 const obj2 = { b: 1, // comment a: 4, c: 5, } // 方法定义也可以作为组内成员参与排序。 const obj3 = { c: 1, d: 2, b () { }, e: 3, } // 空行前后的连续注释均不构成组内分隔,b 组仍有序。 const obj4 = { c: 1, d: 2, // comment // comment b() { }, e: 4 } // 非简单名的计算属性不会破坏分组逻辑。 const obj5 = { b, [foo + bar]: 1, a } // 空行出现在逗号之前同样构成分组边界。 const obj6 = { b: 1 // comment before comma , a: 2 }; // 空行分隔的两个组各自有序,组内展开属性后的排序照常处理。 const obj7 = { b: 1, a: 2, ...z, c: 3 }

该选项的实现细节在源码中相当考究:规则通过sourceCode.getTokensBetween(prevNode, node, { includeComments: true })取出相邻属性之间的全部 Token(含注释),再逐一比较相邻 Token 的行号差,只要存在行号差大于 1(即存在空行)的情况,就判定为"空行分隔"。三处检查分别覆盖:Token 之间、当前节点与最后一个 Token 之间、第一个 Token 与上一个节点之间(见 lib/rules/sort-keys.js 的Property处理器),因此即使空行出现在注释与逗号之间这种特殊位置也能被识别。测试文件中也覆盖了b: 1后换行、注释后再接,a: 2等组合场景。

ignoreComputedKeys 选项示例

ignoreComputedKeys: true时,规则忽略所有计算键,并且不会报告被计算键分隔开的未排序属性——计算键同样起到"重置排序"的作用。

正确的代码["error", "asc", {ignoreComputedKeys: true}]):

/*eslint sort-keys: ["error", "asc", {ignoreComputedKeys: true}]*/ // 计算键位于对象开头,重置后 a 开始新组。 const obj1 = { [b]: 1, a: 2 } // 计算键 [b] 分隔开 c 与 a,二者不再比较。 const obj2 = { c: 1, [b]: 2, a: 3 } // 字符串字面量计算键同样被忽略。 const obj3 = { c: 1, ["b"]: 2, a: 3 }

源码中对应逻辑为:if (ignoreComputedKeys && node.computed) { stack.prevName = null; return; },即遇到计算键先重置排序基准并跳过比较。注意它与默认行为(默认会检查简单名计算键)的差异:默认配置下{c: 1, [b]: 2, a: 3}会因为c > b > a的乱序而报错,开启本选项后才被放行。

底层实现原理:八个比较函数与状态栈

理解 lib/rules/sort-keys.js 的实现,能帮你更精确地预判规则的每一个判断。核心有三块:

  1. 比较函数矩阵:源码维护了一个isValidOrders对象,由"升/降序 × 大小写敏感 × 自然序"组合出 8 个比较函数——ascascIascNascINdescdescIdescNdescINI后缀表示不区分大小写,N后缀表示自然序)。运行时通过order + (insensitive ? "I" : "") + (natural ? "N" : "")拼接出函数名并调用,因此所有配置组合的行为都是确定的、可预期的。
  2. 状态栈(stack):规则在进入ObjectExpression时压入一个栈帧,记录prevName(上一个有效属性名)、prevNode(上一个属性节点)、prevBlankLine(是否空行分隔)、numKeys(属性总数);退出对象时弹出。嵌套对象互不干扰,每个对象字面量都有独立的排序上下文。
  3. 报告消息:当相邻键违反顺序时,规则在node.key.loc位置报告消息"Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",消息中会动态带上naturalinsensitiveascending/descending等修饰词,方便开发者一眼看出违反了哪条排序策略。

在真实项目中如何配置

由于该规则不在eslint:recommended预设中,需要在 ESLint 配置(flat config 或 eslintrc)中显式开启。例如在 flat config 中:

// eslint.config.js export default [ { rules: { "sort-keys": ["error", "asc", { caseSensitive: true, natural: false, minKeys: 2, allowLineSeparatedGroups: false, ignoreComputedKeys: false }] } } ];

实践建议:

  • 希望按语义分组组织大型对象(如国际化文案、表单配置),可开启allowLineSeparatedGroups: trueminKeys: 4,既保持分组可读性,又避免对小型对象过度约束;
  • 项目中存在大量数字后缀键名(如item1item10),推荐natural: true,否则字典序会把item10排在item2之前,产生大量误报;
  • 计算属性较多且无法静态求值(如[keyName]动态键),可开启ignoreComputedKeys: true避免误报;
  • 只想约束团队书写习惯、不阻塞 CI,可将"error"降级为"warn"

When Not To Use It(何时不该使用)

如果你不想让规则提醒属性的排列顺序,可以放心地关闭此规则:

{ "rules": { "sort-keys": "off" } }

例如团队已经约定按"业务重要程度"或"写入顺序"组织对象键,排序反而会制造噪音;此时关闭sort-keys不会影响任何其他功能。

Compatibility(兼容性)

该规则的设计理念与JSCSvalidateOrderInObjectKeys规则一脉相承(JSCS 是 ESLint 的前身生态之一),其排序语义与选项粒度在此基础上做了扩展(如naturalminKeysallowLineSeparatedGroupsignoreComputedKeys均为后续新增能力)。在从 JSCS 迁移到 ESLint 的工程中,sort-keys可以作为validateOrderInObjectKeys的替代方案,建议对照上述选项语义逐一映射,再通过本仓库的 测试用例(共 2556 行、覆盖默认行为到全部选项的合法/非法场景)验证迁移后的行为是否符合预期。

【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询