commitlint 规则配置完全指南:Level、Applicable 与 Value 的三种写法及内置规则全参考
【免费下载链接】commitlint📓 Lint commit messages项目地址: https://gitcode.com/gh_mirrors/co/commitlint
commitlint 通过「规则(Rules)」把提交信息规范化为可校验的约束体系,而每条规则的核心就是一段简单的配置数组。本文以 docs/reference/rules-configuration.md 为骨架,结合@commitlint/rules、@commitlint/lint、@commitlint/execute-rule等源码实现,系统讲解规则配置的三要素(Level / Applicable / Value)、三种等价写法(普通数组、函数、异步函数),并给出 rules.md 中全部内置规则的参数与默认值参考,帮助你读懂并编写任何一份 commitlint 配置。
规则的本质:名称 + 配置数组
在 commitlint 中,一条规则由「规则名称」和「配置数组」两部分构成,所有规则统一放在配置文件的rules对象下,键名即规则名。配置数组固定包含至多三个元素:
| 位置 | 字段 | 取值 | 含义 |
|---|---|---|---|
| 第 1 个 | Level(级别) | 0、1、2 | 0关闭规则;1视为警告(warning);2视为错误(error) |
| 第 2 个 | Applicable(适用条件) | always、never | never表示反转规则的判定逻辑 |
| 第 3 个 | Value(取值) | 任意(数字、字符串、数组、对象等) | 规则校验时使用的参数 |
最小合法的配置只写前两个元素,例如"header-max-length": [2, "always"];而"header-max-length": [0, "always", 72]则同时给出了级别、条件和上限值 72。
这些语义在 lint 校验实现 中有严格的强制约束:配置必须是长度为 2 或 3 的数组;level必须是0~2之间的数字;when必须是字符串且只能是"always"或"never"。如果违反这些约束,lint 会直接抛出明确的错误信息(例如config for rule xxx must be array、condition for rule xxx must be "always" or "never"),而不是静默忽略。
在 TypeScript 类型层面,级别常量在@commitlint/types中被定义为枚举RuleConfigSeverity:Disabled = 0、Warning = 1、Error = 2。官方配置包 config-conventional 就大量使用这套常量,例如:
"body-leading-blank": [RuleConfigSeverity.Warning, "always"], "header-max-length": [RuleConfigSeverity.Error, "always", 100], "subject-empty": [RuleConfigSeverity.Error, "never"],三种等价的配置写法
规则配置既可以是普通数组,也可以是「返回数组的函数」,甚至是「返回 Promise 的异步函数」。也就是说,rules对象上每个键的值,可以是以下任意一种:
type Config<T> = | T // 普通数组,例如 [2, "always", 72] | Promise<T> // 直接给一个 Promise(较少见) | (() => T) // 同步函数,返回数组 | (() => Promise<T>); // 异步函数,返回 Promise<array>这种「函数式配置」的用途在于:规则值可以动态计算。例如根据环境变量、读取到的文件内容或异步查询结果来决定某个阈值,而不是在配置文件中写死。
1. 普通数组(Plain array)
最常见、最直观的写法,直接把配置数组写在规则名下:
export default { // ... rules: { "header-max-length": [0, "always", 72], // 关闭该规则(演示用),正常应设为 1 或 2 }, // ... };[0, "always", 72]表示:级别为0(禁用)、条件为always、上限值为72。这是原文档给出的标准示例格式。
2. 函数返回数组(Function returning array)
把配置数组包裹在箭头函数中,lint 执行时调用该函数拿到数组:
export default { // ... rules: { "header-max-length": () => [0, "always", 72], // 函数式写法,效果同上 }, // ... };3. 异步函数返回数组(Async function returning array)
当取值需要异步获取时(例如从远端拉取团队约定、读取数据库中的历史数据),可以使用async函数:
export default { // ... rules: { "header-max-length": async () => [0, "always", 72], // 异步函数,返回 Promise<array> }, // ... };从源码结构看,execute-rule 包 正是这三种写法的统一执行器:它先判断配置是否为函数(typeof config === "function"),是函数就调用它,否则包一层async () => config,最终await拿到真正的数组,因此无论你写哪种形式,结果完全等价。而在 lint.ts 中,最终都会以const [level, when, value] = config解构出三要素,再调用allRules.get(name)找到规则实现函数执行。level 为0的规则会被直接过滤跳过(config[0] > 0才参与校验),这正对应「0表示关闭规则」的行为。
规则校验的整体流程
理解了配置写法后,把整条链路串起来看会更清晰。以commitlint命令校验一条提交信息为例,核心调用链如下:
- @commitlint/lint 接收提交信息与
rules配置; - 如果配置了某个规则名但没有对应实现,会抛出
RangeError,列出「缺少实现的规则」与「支持的规则全集」; - 逐条校验配置数组的合法性(长度、级别、条件),不合法直接抛错;
- 过滤掉 level 为
0的规则后,对每条规则调用其实现函数(parsed, when, value); - 汇总所有结果,level 为
2且不通过的产生errors,level 为1且不通过的产生warnings;只要存在 error,整体valid即为false。
内置规则实现的注册表在 rules/index.ts,例如"header-max-length": headerMaxLength、"scope-enum": scopeEnum、"breaking-change-exclamation-mark": breakingChangeExclamationMark等,共 33 个内置规则。
规则实现中的细节:when 如何反转判定
「Applicable」字段always/never的实现方式值得留意。以内置规则源码为例,never并不是简单取反整个表达式,而是在规则内部对语义做了精细化处理:
- type-enum(type-enum.ts):
always要求 type 必须在枚举列表中,never则要求 type 不能出现在列表中,错误信息也会相应插入not。 - subject-empty(subject-empty.ts):
always要求 subject 为空,never要求 subject 非空——[2, "never"]即「subject 不能为空」。 - subject-case(subject-case.ts):当
never模式下某个 case 匹配导致失败时,错误信息只报告实际命中的 case;always模式下失败则报告所有配置的 case。同时该规则要求 subject 首字符必须是 Unicode 字母(\p{Ll}\p{Lu}\p{Lt}),非字母开头直接放行。
内置规则完整参考(含默认值)
下面汇总 rules.md 中全部内置规则,按提交信息的组成部分(body / header / footer / scope / subject / type / 整体)分组,并给出「判定条件」与「默认值/取值范围」。规则配置中的 value 会覆盖默认值。
body 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
body-case | body属于 value 指定的大小写格式 | always | 'lower-case',可选lower-case / upper-case / camel-case / kebab-case / pascal-case / sentence-case / snake-case / start-case |
body-empty | body是否为空 | never | — |
body-full-stop | body是否以 value 结尾 | never | '.' |
body-leading-blank | body是否以空行开头 | always | — |
body-max-length | body字符数不超过 value | always | Infinity |
body-max-line-length | body每行不超过 value(含 URL 的行豁免) | always | Infinity |
body-min-length | body字符数不少于 value | always | 0 |
footer 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
footer-empty | footer是否为空 | never | — |
footer-leading-blank | footer是否以空行开头 | always | — |
footer-max-length | footer字符数不超过 value | always | Infinity |
footer-max-line-length | footer每行不超过 value | always | Infinity |
footer-min-length | footer字符数不少于 value | always | 0 |
header 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
header-case | header属于 value 指定的大小写格式 | always | 'lower-case'(可选值同body-case) |
header-full-stop | header是否以 value 结尾 | never | '.' |
header-max-length | header字符数不超过 value | always | 72 |
header-min-length | header字符数不少于 value | always | 0 |
header-trim | header首尾不能有空白字符 | always | — |
scope 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
scope-case | scope属于 value 指定的大小写格式 | always | 'lower-case';也支持对象写法{ cases: ["kebab-case"], delimiters: ["/"] } |
scope-delimiter-style | scope中出现的所有分隔符必须属于 value | always | ["/", "\\", ","] |
scope-empty | scope是否为空 | never | — |
scope-enum | scope必须(always)/ 不得(never)出现在 value 中 | always | [];支持对象写法{ scopes: ["foo", "bar"], delimiters: ["/"] } |
scope-max-length | scope字符数不超过 value | always | Infinity |
scope-min-length | scope字符数不少于 value | always | 0 |
关于多段 scope(multi-segment scope)的几个要点(来自 rules.md 与源码 scope-enum.ts、scope-case.ts):
delimiters默认为["/", "\\", ","],用于把scope按分隔符拆成多段分别校验;逗号会按, ?(允许空格)处理,其余分隔符会被正则转义。scope-enum在「提交信息没有 scope」或「value 为空数组」时始终通过;always要求所有 scope 段都在枚举中,never要求所有 scope 段都不在枚举中。- 使用
scope-delimiter-style时,若同时使用scope-enum/scope-case,务必在这些规则里配置相同的delimiters,否则 scope 的解析可能不一致。
subject 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
subject-case | subject不得(never)/ 必须(always)属于 value 指定格式 | never | ["sentence-case", "start-case", "pascal-case", "upper-case"](可选值同body-case) |
subject-empty | subject是否为空 | never | — |
subject-exclamation-mark | subject在:前是否带! | never | — |
subject-full-stop | subject是否以 value 结尾 | never | '.' |
subject-max-length | subject字符数不超过 value | always | Infinity |
subject-min-length | subject字符数不少于 value | always | 0 |
type 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
type-case | type属于 value 指定格式 | always | 'lower-case'(可选值同body-case) |
type-empty | type是否为空 | never | — |
type-enum | type必须(always)/ 不得(never)出现在 value 中 | always | ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"] |
type-max-length | type字符数不超过 value | always | Infinity |
type-min-length | type字符数不少于 value | always | 0 |
整条 message 相关
| 规则 | 判定条件 | 默认规则 | 默认 value |
|---|---|---|---|
breaking-change-exclamation-mark | header 的:前带!与 footer 中的^BREAKING[ -]CHANGE:要么同时存在、要么同时不存在(XNOR 行为) | always | — |
references-empty | references是否有条目 | never | — |
signed-off-by | message中是否包含 value | always | 'Signed-off-by:' |
trailer-exists | message中是否存在 value 指定的 trailer | always | 'Signed-off-by:' |
其中几个规则的实现细节值得说明:
- breaking-change-exclamation-mark(breaking-change-exclamation-mark.ts):header 与 footer 均为空时直接通过;否则用正则
^(\w*)(?:\((.*)\))?!: (.*)$检查 header 是否带!,用/^BREAKING[ -]CHANGE:/m检查 footer。hasExclamationMark === hasBreakingChange即 XNOR:两者同时存在或同时不存在才通过。 - trailer-exists(trailer-exists.ts):实现上会调用
git interpret-trailers --parse子进程解析 trailer,因此依赖本机 Git 环境。 - signed-off-by与trailer-exists的默认 value 都是
'Signed-off-by:',但前者检查的是整条 message 文本,后者检查的是解析出的 trailer 行,语义略有差异。
一份可落地的完整配置示例
把三种写法组合进同一份配置中(基于 config-conventional 的默认风格扩展):
export default { extends: ["@commitlint/config-conventional"], rules: { // 普通数组写法:header 最长 72 字符,超长报 error "header-max-length": [2, "always", 72], // 函数写法:值可以动态计算 "subject-case": () => [2, "never", ["sentence-case", "start-case", "pascal-case", "upper-case"]], // 异步函数写法:适合从外部动态获取枚举 "scope-enum": async () => [2, "always", ["core", "cli", "docs"]], // 利用 never 反转语义 "subject-empty": [2, "never"], // subject 不能为空 "subject-full-stop": [2, "never", "."], // subject 不能以句点结尾 // 多段 scope 场景 "scope-case": [2, "always", { cases: ["kebab-case"], delimiters: ["/"] }], "scope-enum": [2, "always", { scopes: ["core/utils", "cli/parser"], delimiters: ["/"] }], }, };需要注意:同一规则名在对象中只能出现一次,因此「异步动态获取 scope-enum」与「对象式 scope-enum」需要按实际场景二选一。另外,extends与自定义rules合并时,自定义规则会覆盖被继承配置中同名规则。
小结
规则配置是 commitlint 中最基础也最灵活的机制:Level控制规则的开关与严重程度,Applicable控制判定的正反方向,Value提供校验参数;而「数组 / 函数 / 异步函数」三种写法由 execute-rule 统一归一化,让配置既可以静态声明,也可以动态计算。配合 rules.md 中的完整规则清单与 lint 源码 的严格校验,你可以精确掌控团队提交信息的每一个细节。
【免费下载链接】commitlint📓 Lint commit messages项目地址: https://gitcode.com/gh_mirrors/co/commitlint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考