ESLint 代码规范(Code Conventions):eslint-config-eslint 配置体系完全指南
本文以 ESLint 仓库中的 code-conventions.md 为骨架,结合仓库内 eslint-config-eslint 包的完整源码,系统梳理 ESLint 项目自身的代码规范:规范由谁定义、为什么采用这套规范、每个配置入口如何使用,以及每条规则的出处与底层实现。读完本文,你既能理解 ESLint 团队维护自己代码的方式,也能直接复用这套配置到自己的项目中。
规范从何而来:eslint-config-eslint
ESLint 项目的代码规范并非零散写在文档里,而是由一个独立发布的共享配置包 eslint-config-eslint 统一定义。文档 code-conventions.md 明确写道:代码规范由 eslint-config-eslint 决定("Code conventions for ESLint are determined by eslint-config-eslint")。
该包位于仓库 packages/eslint-config-eslint 目录下,其 package.json 中这样描述自己:
"Default ESLint configuration for ESLint projects."
即「ESLint 项目默认的 ESLint 配置」。这意味着:
- ESLint 团队自己写代码时,就是被这套配置约束的;
- 仓库根目录的 eslint.config.js 直接引用了该包;
- 任何 ESLint 维护的项目(包括文档站、示例代码)都遵循同一套规范。
为什么规范由配置包而不是文档定义
把规范下沉到可执行的配置包,带来三个直接收益:
- 单一事实来源:规则清单、取值、参数都在源码里,而不是散落在人写的文档中,不会出现「文档说 A、代码用 B」的漂移;
- 可自动执行:规范不是给人「参考」的,而是由 ESLint 在 CI 中强制执行的;
- 可复用发布:包通过 npm 独立发布(版本号见 package.json,当前仓库中为
14.0.0),任何项目都能安装使用。
规范背后的依赖:六个插件与共享配置
eslint-config-eslint 并不是凭空规定几百条规则,而是站在多个高质量插件的基础上做减法与微调。其 package.json 的 dependencies 揭示了全部依赖:
| 依赖 | 作用 |
|---|---|
@eslint/js |
ESLint 官方核心规则的 flat config 预设(js.configs.recommended) |
eslint-plugin-jsdoc |
JSDoc 注释规范(类型、标签、描述、缩进) |
eslint-plugin-unicorn |
现代 JavaScript 风格建议(数组、字符串 API 偏好) |
@eslint-community/eslint-plugin-eslint-comments |
ESLint 指令注释(eslint-disable 等)的规范 |
eslint-plugin-regexp |
正则表达式规范 |
eslint-plugin-n |
Node.js 环境专属规范(回调、require、协议前缀) |
此外还有两个关键元信息:
peerDependencies声明eslint: ^10.0.0(可选的 peer 依赖,即使用方自己安装 ESLint);engines要求node: ^20.19.0 || ^22.13.0 || >=24,说明这套配置基于现代 Node.js 与 ESLint 10 的 flat config 体系。
从源码结构看,index.js 是包的主入口,它将 base 配置与 ESM/CJS 专属配置组合后导出:
module.exports = [
...baseConfigs,
...esmConfigs.map(config => ({
files: ["**/*.js"], // ESM 专属配置作用于 .js 文件
...config,
})),
...cjsConfigs.map(config => ({
files: ["**/*.cjs"], // CommonJS 专属配置作用于 .cjs 文件
...config,
})),
];
可以推断:在 "type": "module" 的项目里 .js 按 ESM 处理,.cjs 显式按 CommonJS 处理;在 CommonJS 项目里则直接使用 cjs 入口。
规则的出处:为什么「看规则文档」就够了
文档强调,任何一条规则的 rationale(设计理由)都可以通过查阅该规则的文档获得:
- 若规则是 ESLint 核心规则,看仓库 docs/src/rules 下的对应页面,例如
no-var、prefer-const等规则页都解释了「为什么不允许这种写法」; - 若规则来自第三方插件(如
unicorn/*、jsdoc/*、n/*),则查阅对应插件的官方文档。
这一点对贡献者尤其重要:当你想修改某条规则或对某个配置提出异议时,第一步不是改配置,而是先理解该规则背后要解决的问题。ESLint 仓库中所有核心规则的实现在 lib/rules 下,例如 prefer-const 的检测逻辑在 lib/rules/prefer-const.js,文档在 docs/src/rules/prefer-const.md,二者一一对应。
配置入口总览:base / cjs / formatting / 默认
eslint-config-eslint 通过 package.json 的 exports 暴露了四个入口:
| 入口 | 文件 | 适用场景 |
|---|---|---|
eslint-config-eslint(默认) |
index.js | Node.js 项目,同时覆盖 .js(ESM)与 .cjs(CJS) |
eslint-config-eslint/base |
base.js | 非 Node.js 环境(浏览器脚本等),不含 Node 专属规则 |
eslint-config-eslint/cjs |
cjs.js | CommonJS 项目(或文件),base + Node CJS 规则 |
eslint-config-eslint/formatting |
formatting.js | 格式化类规则,与任意上述配置叠加使用 |
其中默认入口与 cjs 入口都复用了 base:
formatting 入口独立成包,是因为文档明确说明:上述所有配置都不包含格式化规则("none of the above configurations includes formatting rules"),是否需要格式化由项目自行决定、按需叠加。
base 配置:核心规则的逐条解读
base.js 是整套规范的「地基」,由五个区块组成,按 index.js 的导出顺序 分别是:基础 linter 选项、unicorn 配置、jsdoc 配置、eslint-comments 配置、regexp 配置、核心 JS 规则。
1. 基础 linter 选项
{
name: "eslint-config-eslint/base",
linterOptions: {
reportUnusedDisableDirectives: "error",
reportUnusedInlineConfigs: "error",
},
}
ESLint 团队把「无用指令」当作错误处理:如果代码里存在多余的 // eslint-disable-line 或内联配置(inline config),直接报错。这保证了仓库里不会残留过期的禁用注释——这是对 @eslint-community/eslint-plugin-eslint-comments 规则的补充,从 Linter 层面兜底。
2. 核心 JS 规则(js.configs.recommended 之上)
区块 eslint-config-eslint/js 以 @eslint/js 的 recommended 预设为基底,再叠加约 60 条额外规则,全部为 error 级别。按主题分组如下:
变量与声明
no-var、prefer-const、prefer-destructuring不在列表但继承自 recommended;显式补充no-undef: ["error", { typeof: true }](即使typeof x也要求x已声明)、no-undef-init、no-undefined、no-use-before-define;no-unused-vars配置为{ vars: "all", args: "after-used", caughtErrors: "all" },即所有变量、函数名都检查,参数「使用之后的位置」才算使用,catch子句错误参数也必须被使用;no-underscore-dangle默认禁止下划线开头/结尾,但允许this._foo这种类私有字段惯例(allowAfterThis: true)。
函数与作用域
func-style: ["error", "declaration"]:强制函数声明而非函数表达式;consistent-return、default-case、default-case-last、default-param-last、no-param-reassign、no-inner-declarations、no-loop-func、no-shadow;prefer-arrow-callback、prefer-rest-params、prefer-spread:现代化函数书写。
严格相等与类型安全
eqeqeq: "error":强制===/!==;yoda: ["error", "never", { exceptRange: true }]:变量在左、字面量在右,但区间判断1 < x && x < 5除外;radix、require-unicode-regexp、prefer-numeric-literals、prefer-exponentiation-operator、no-loss-of-precision(继承自 recommended)。
对象与字符串
object-shorthand: ["error", "always", { avoidExplicitReturnArrows: true }]:强制简写,但对象方法简写里不鼓励返回箭头函数;prefer-template、no-useless-concat、no-useless-computed-key、prefer-regex-literals。
安全与危险 API
no-eval、no-implied-eval、no-new-func、no-new-wrappers、no-extend-native、no-proto、no-caller、no-iterator、no-alert、no-console、no-script-url、no-return-assign、no-sequences、no-throw-literal。
一个体现项目风格的细节:no-restricted-properties
该规则禁止使用 assert 模块的宽松相等方法,并给出明确的替代提示:
"no-restricted-properties": [
"error",
{ object: "assert", property: "equal", message: "Use assert.strictEqual instead of assert.equal." },
{ object: "assert", property: "notEqual", message: "Use assert.notStrictEqual instead of assert.notEqual." },
{ object: "assert", property: "deepEqual", message: "Use assert.deepStrictEqual instead of assert.deepEqual." },
{ object: "assert", property: "notDeepEqual", message: "Use assert.notDeepStrictEqual instead of assert.notDeepEqual." },
],
这体现了 ESLint 团队的一个核心约定:所有测试断言必须使用严格相等。对应地,根目录 eslint.config.js 中的 eslint/tests 区块还额外禁止了 assert.doesNotThrow()(要求用代码旁注释替代),两条规则共同约束着 tests 目录下几百个测试文件。
其他值得注意的取舍
no-process-exit: "off":Node 工具脚本中允许直接process.exit();strict: ["error", "global"]:全局严格模式(现代 ESM 天然严格,CJS 文件通过全局指令开启);no-unneeded-ternary、no-else-return: ["error", { allowElseIf: false }]、prefer-promise-reject-errors等规整控制流。
3. JSDoc 规范(eslint-plugin-jsdoc)
ESLint 是一个重度使用 JSDoc 的项目——所有核心规则文件都以 /** @type {import("eslint").Linter.Rule} */ 之类的注释开头。eslint-config-eslint/jsdoc 区块在插件的 flat/recommended 之上做了三件事:
设置(settings):mode: "typescript"(按 TypeScript 风格解析 JSDoc);标签偏好 file -> fileoverview、augments -> extends、class -> constructor;类型偏好里最值得注意的一条——不鼓励 *、any、object、function 等泛泛类型,要求写具体类型:
preferredTypes: {
"*": { message: "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", replacement: "any" },
object: { message: "Use the specific object type or `Object` if truly arbitrary", replacement: "Object" },
function: { message: "Point to a `@callback` namepath or `Function` if truly arbitrary in form", replacement: "Function" },
Promise: { message: "Specify the specific Promise type, including, if necessary, the type `any`" },
array: "Array",
...
}
规则开关:jsdoc/check-syntax、jsdoc/no-bad-blocks、jsdoc/require-asterisk-prefix、jsdoc/require-description、jsdoc/require-throws、jsdoc/tag-lines 等设为 error;而 no-defaults、reject-any-type、no-undefined-types、require-yields、require-throws-type 显式关闭,避免过度约束。
格式细节:jsdoc/require-hyphen-before-param-description: ["error", "never"](参数描述前不要连字符)、jsdoc/tag-lines 要求 @example 与描述之间留空行、@fileoverview 可任意,其余标签行内不留空行。
4. ESLint 指令注释规范(eslint-comments)
区块 eslint-config-eslint/eslint-comments 采用插件的 recommended 预设,并额外要求:
disable-enable-pair:// eslint-disable必须成对出现(不能只关不开);require-description:每条eslint-disable指令必须写明原因,例如// eslint-disable-next-line no-console -- 调试输出。
结合前面 reportUnusedDisableDirectives: "error",ESLint 仓库对「禁用指令」的管理堪称苛刻:必须有原因、必须成对、必须真的被用到。
5. 正则与 Unicorn 规则
regexp.configs.recommended:采用 eslint-plugin-regexp 的推荐预设,规范正则写法(配合核心规则require-unicode-regexp,所有正则必须带u标志);unicorn区块只开启 11 条「现代 API 偏好」规则,例如prefer-array-find(用find而非filter()[0])、prefer-array-flat、prefer-includes、prefer-set-has、prefer-string-starts-ends-with、prefer-at等——只取最通用、争议最小的子集,而非全量开启 unicorn。
Node.js 专属配置:ESM 与 CJS 的差异
nodejs.js 定义了 ESM 与 CJS 两套配置,二者共享三条规则:
const sharedRules = {
"n/callback-return": ["error", ["cb", "callback", "next"]], // 回调后必须 return
"n/handle-callback-err": ["error", "err"], // 回调错误参数必须处理
"n/prefer-node-protocol": "error", // 用 node: 前缀导入内置模块
};
差异在于 CJS 额外启用了三条 require 相关规则(CommonJS 才有的 require 机制):
n/no-mixed-requires:同一语句中禁止混用同步require与变量声明;n/no-new-require:禁止new require(...);n/no-path-concat:禁止用字符串拼接构造模块路径(应用path.join)。
ESM/CJS 的基底分别来自 eslint-plugin-n 的 flat/recommended-module 与 flat/recommended-script。这套「共享 + 差异」的设计在默认入口 index.js 中被具象化为:.js 文件挂 ESM 配置、.cjs 文件挂 CJS 配置。
formatting 配置:格式化规则的逐条解读
formatting.js 独立导出一个 rules 对象,集中了全部纯排版规则,且全部为 error 级。最核心的几组:
缩进与换行
indent: ["error", 4, { SwitchCase: 1 }], // 4 空格缩进,switch case 再缩进 1 级
"max-len": ["error", 160, { ignoreComments: true, ignoreUrls: true, ignoreStrings: true, ignoreTemplateLiterals: true, ignoreRegExpLiterals: true }],
"no-multiple-empty-lines": ["error", { max: 2, maxBOF: 0, maxEOF: 0 }], // 最多连续 2 空行,文件首尾禁止空行
引号与分号
quotes: ["error", "double", { avoidEscape: true }], // 双引号;字符串内含双引号时允许用单引号
semi: "error", // 必须分号
"comma-dangle": "error", // 多行时必须有尾逗号
"quote-props": ["error", "as-needed"], // 对象键只有在必须时才加引号
括号与空格
"arrow-parens": ["error", "as-needed"], // 单参数箭头函数省略括号
"space-before-function-paren": ["error", { anonymous: "never", named: "never", asyncArrow: "always" }],
"object-curly-spacing": ["error", "always"], // 对象花括号内侧必须有空格
"object-curly-newline": ["error", { consistent: true, multiline: true }],
"template-curly-spacing": ["error", "never"], // 模板字符串 ${} 内不留空格
"spaced-comment": ["error", "always", { exceptions: ["-"] }], // 注释 // 或 /* 后必须有空格,允许 "-" 例外
分组排版约定
padding-line-between-statements 强制「变量声明块与后续语句之间必须有空行,但连续变量声明之间可无空行」:
"padding-line-between-statements": [
"error",
{ blankLine: "always", prev: ["const", "let", "var"], next: "*" },
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] },
],
另外还有 brace-style: ["error", "1tbs"](大括号一行式)、operator-linebreak、wrap-iife、yield-star-spacing、generator-star-spacing 等常规排版规则。这套格式化配置与仓库实际代码风格完全一致——例如 base.js 本身就用 4 空格缩进、双引号、尾逗号。
在项目中使用:安装与配置示例
安装
先安装 ESLint,再安装本配置(README.md 提供了 npm / yarn / pnpm / bun 四种方式):
npm install eslint -D
npm install eslint-config-eslint -D
注意 eslint-config-eslint 的 peerDependencies 要求 ESLint ^10.0.0,因此请使用支持 flat config 的 ESLint 10 及以上版本,Node 版本不低于 ^20.19.0 || ^22.13.0 || >=24。
场景一:ESM 项目("type": "module")
// eslint.config.js
import { defineConfig } from "eslint/config";
import eslintConfigESLint from "eslint-config-eslint";
export default defineConfig([eslintConfigESLint]);
默认入口会自动处理 .js(ESM)与 .cjs(CJS)两类文件。
场景二:CommonJS 项目
// eslint.config.js
const { defineConfig } = require("eslint/config");
const eslintConfigESLintCJS = require("eslint-config-eslint/cjs");
module.exports = defineConfig([eslintConfigESLintCJS]);
场景三:base 配置(非 Node.js 文件)
base 配置不含任何 Node.js 专属规则,适合浏览器端脚本。README 给出的混合示例非常典型——浏览器脚本用 base,Node 工具与配置文件用 cjs:
const { defineConfig } = require("eslint/config");
const eslintConfigESLintBase = require("eslint-config-eslint/base");
const eslintConfigESLintCJS = require("eslint-config-eslint/cjs");
module.exports = defineConfig([
{
files: ["scripts/*.js"], // 浏览器脚本:base
extends: [eslintConfigESLintBase],
},
{
files: ["eslint.config.js", ".eleventy.js", "tools/*.js"], // Node 环境:cjs
extends: [eslintConfigESLintCJS],
},
]);
场景四:叠加格式化规则
import { defineConfig } from "eslint/config";
import eslintConfigESLint from "eslint-config-eslint";
import eslintConfigESLintFormatting from "eslint-config-eslint/formatting";
export default defineConfig([eslintConfigESLint, eslintConfigESLintFormatting]);
formatting 是可选的最后一块拼图——ESLint 官方将此与「编辑器格式化」职责区分:格式化规则交给 ESLint 执行,但默认不强制。
真实案例:ESLint 仓库如何自用这套规范
仓库根目录 eslint.config.js 是这套配置的最佳实战范本。它先引入 eslint-config-eslint/cjs 作为全量基底:
const eslintConfigESLintCJS = require("eslint-config-eslint/cjs");
// ...
module.exports = defineConfig([
{
name: "eslint/cjs",
files: [ALL_JS_FILES], // "**/*.js"
extends: [eslintConfigESLintCJS],
},
// ... 后续区块
]);
然后基于不同文件类型叠加数百行「项目专属」规则,可看到对上述配置体系的典型用法:
- 忽略策略:
globalIgnores排除docs、templates、tests/fixtures等目录,因为示例代码、生成文件不参与 lint; - 规则叠加与豁免:
eslint/tools区块对tools/*.js和docs/tools/*.js关闭no-console与n/no-process-exit(工具脚本允许打印与退出);eslint/deprecated-rules对已废弃规则文件放宽插件约束; - 插件式扩展:对
lib/rules/*.js挂载eslint-plugin插件的rules-recommended,要求每条核心规则文件具备规范的meta信息与文档描述格式(如report-message-format要求报错信息首字母大写、以句号结尾); - 目录隔离:用
n/no-restricted-require限制lib内部模块的跨目录require,保证分层架构不被破坏; - 多语言文件:对 JSON/JSONC/JSON5 使用
@eslint/json的语言级配置,对*.ts使用 TypeScript parser,对 YAML 挂载 eslint-plugin-yml。
这套「通用基底 + 按目录/语言微调」的写法,正是 ESLint 团队推荐给大型仓库的实践:公共规范由 eslint-config-eslint 统一维护,项目专属规则放在自己的配置文件中覆盖。
修改规范的正确姿势:从理解到提议
文档对「想改规范的人」给出了明确指引:先看规则的官方文档理解其设计意图(核心规则见 docs/src/rules,插件规则见插件文档),再做决定。这与仓库中 propose-rule-change.md 等流程文档的理念一脉相承——规范是集体共识的执行化,不是个人的口味偏好。
如果改动涉及 package.json,文档特别提示参见 package-json-conventions.md。这份配套规范同样非常具体,例如:脚本名只允许小写字母、:(分隔部分)、-(分隔单词)、+(分隔文件扩展名);脚本必须按字母序排列;lint、test、build、fetch、fmt、release、start 各有明确语义;lint:fix、fmt:check 等修饰符有严格顺序(如 lint:fix:js:watch 而不是 lint:watch:js:fix)。代码规范(怎么写字)与包工程规范(怎么组织脚本)共同构成了 ESLint 的完整开发约定。
小结
- 规范主体:
eslint-config-eslint(packages/eslint-config-eslint),由文档 code-conventions.md 指定为唯一权威来源; - 四个入口:默认(ESM+CJS 自动分流)、
/base(非 Node 环境)、/cjs(CommonJS)、/formatting(可选格式化); - 六类规则来源:
@eslint/js推荐预设 + jsdoc + unicorn + eslint-comments + regexp + eslint-plugin-n,再叠加约百条定制规则; - 三大特色约定:严格相等断言(禁
assert.equal等)、禁用指令必须有原因且不得冗余、JSDoc 类型必须具体; - 实战范本:根目录 eslint.config.js 展示了如何在自己的仓库中组合这套配置并叠加项目专属规则。
无论你是 ESLint 贡献者还是普通使用者,都可以把 eslint-config-eslint 当作一套「高质量 JavaScript 工程默认值」来参考或直接采用——它既是 ESLint 团队的自律工具,也是一份可迁移到任意项目的规范模板。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.2 K634
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown300
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java101
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java60
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript60
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python280