Ladybird 代码策略:测试、语言、代码规范与自动化 Lint 钩子的完整实践
本文基于 Ladybird 浏览器仓库的 Documentation/CodePolicy.md 展开,系统梳理该独立浏览器项目对上游代码的准入要求:从测试策略、人类语言规范,到 C++23 代码风格、提交信息格式、AI 辅助使用边界,以及 pre-commit 钩子和 git notes 的实际工作方式。读完后,你将了解 Ladybird 贡献代码时必须满足哪些硬性规则,这些规则是如何被 Meta/lint-ci.sh 和 Meta/lint-commit.sh 等脚本自动强制执行的。
测试策略:修 Bug 加功能必须附带测试
CodePolicy 的第一条原则是:在可能的情况下,代码变更在修复缺陷或新增功能时应附带测试。对于与 Web 平台相关的改动,策略特别强调 Web Platform Tests(WPT):如果某个改动使 Ladybird 通过了之前尚未通过的 WPT 测试,应考虑把对应测试导入 Ladybird 代码树,并随代码改动一并提交。
导入 WPT 的操作方式在 Documentation/Testing.md 中有完整说明,核心命令是:
./Meta/WPT.sh import html/dom/aria-attribute-reflection.html
即把目标 WPT 测试在 http://wpt.live/ 上的路径部分传给 ./Meta/WPT.sh import。脚本会下载该测试及其 JavaScript 依赖,复制到 Tests/LibWeb/<test-type>/input/wpt-import 目录,运行测试后在 Tests/LibWeb/<test-type>/expected/wpt-import 下生成期望结果文件。WPT 的日常运行也统一通过仓库根目录的 Meta/WPT.sh 完成(如 run、compare、update 子命令)。
人类语言策略:把英文当作编程语言一样对待
Ladybird 明确要求“对人类语言的态度要和对编程语言一样严肃”。该政策适用于所有用户可见字符串、代码、注释和提交信息:
- 项目官方语言为美式英语,日期使用 ISO 8601 格式,单位使用公制;
- 使用正确的拼写、语法和标点;
- 采用权威、技术性的语气;
- 避免缩写(contractions)、俚语和习语;
- 避免幽默、讽刺及其他非字面含义的表达;
- 使用性别中立的代词(指代特定个人时除外)。
文档特别指出:这条规则同样适用于调试日志等内部字符串,因为它们未来可能被暴露给用户。
代码策略(Do):C++23、AK 容器与原子提交
CodePolicy 给出了明确的“应该做”清单,核心要点包括:
- 写符合项目风格的惯用 C++23,在所有代码中使用
AK容器。AK 是仓库根目录 AK/ 下的基础库,提供了Vector、HashMap、String等现代容器与智能指针(如 OwnPtr、RefPtr),是 Ladybird 代码的基础设施。 - 遵循 Documentation/CodingStyle.md 的编码风格,并用
clang-format自动格式化 C++ 文件。该文档强调必须使用正确版本的clang-format,CI 强制的版本由 Meta/Linters/lint_clang_format.py 指定。命名规范上:类/结构体/命名空间用 CamelCase,变量和函数用 snake_case,常量用 SCREAMING_CASE;实现规范算法时尽量与规范原文命名保持一致。 - 选择有表达力的变量、函数与类名,让代码意图尽可能明显。
- 将改动拆分为独立的原子提交(一个提交对应一个功能或修复,且构建、测试、系统整体可用)。
- 确保提交已经 rebase 到 master 分支。
- 提交信息按 72 字符换行。
- 提交信息第一行是主题行,格式必须为
Category: Brief description of what's being changed,其中 Category 是库、应用、服务或工具的名字。文档给出的规则细节包括:- 示例类别:
LibMedia、WebContent、CI、AK、RequestServer、js; - 不要使用
Libraries、Utilities这类过大的目录名作为类别(除非是确实影响大片代码的通用改动); - 也不要用具体组件名(如 C++ 类名)做类别——应写
LibGUI: Brief description of what's being changed in FooWidget,而不是FooWidget: ...; - 多个类别可用
+组合,如LibJS+LibWeb+Browser: ...。
- 示例类别:
- 主题行使用祈使句("Foo: Change the way dates work" 而不是 "Foo: Changed ...")。
- 提交信息用规范的英文、仔细的措辞和标点撰写。
- 评审后追加改动时,在合适的情况下 amend 已有提交。
- 推送修复后,把相应的评审评论标记为 “resolved”。
- 对文件做实质性修改时,添加个人版权行(可选但鼓励)。
- 检查代码、注释和提交信息的拼写。
- 随代码提交的图片要运行
optipng -strip all优化并剥离无用元数据——文档称这能把文件大小从几 KB 压到几百字节。
这条 optipng 要求在 CI 侧有对应的强制检查:Meta/Linters/check_png_sizes.sh 会对 git 跟踪的每个 PNG(wpt-import 目录除外)运行 optipng -strip all,如果剥离出的字节数达到阈值(环境变量 MINIMUM_OPTIMIZATION_BYTES,默认 1024 字节)就判定未优化并失败,提示开发者“请对修改过的 PNG 运行 optipng -strip all”。本地若未安装 optipng 该检查会跳过,但在 CI(GITHUB_ACTIONS 环境)中缺失 optipng 会直接失败。
代码策略(Don't):明确禁止的反模式
文档同样列出了禁止事项,每一条都有明确的工程动机:
- 不要引入与项目许可证(2-clause BSD)不兼容的改动。仓库根目录的 LICENSE 即为 BSD 2-Clause,且 Meta/Linters/check_style.py 会校验源文件头部的版权注释必须符合
Copyright (c) YYYY(-YYYY), ...加SPDX-License-Identifier: BSD-2-Clause的固定格式,否则 lint 失败。 - 不要触碰改动范围之外的任何内容(保持原子性)。
- 不要跨多个提交反复迭代设计(设计应在提交前成熟)。
- 不要用 "refactor"、"fix" 这类含糊词逃避解释具体改了什么。
- 主题行结尾不要加句号。
- 不要包含被注释掉的代码。
- 不要用 C 的方式写代码——应充分利用 C++ 的特性,不要自我限制在标准 C 库内。
- 不要在熟悉系统之前就尝试大型架构改动。
- 不要做过度的“风水编程”——没有可量化收益地移动代码。
- 不要在用户可见部分加入玩笑或“有趣”的东西。
关于 AI 与 LLM 的使用边界
文档对 AI 辅助的立场务实而明确:使用 AI 辅助通常没问题,但使用者有责任确保输出达到项目标准。文档指出现有 AI 生成内容往往过于冗长,质量不如精心撰写的人工代码。两条红线是:
- 不得用 AI 或 LLM 直接生成改动、回复 issue 或参与项目讨论,而不同时以人工内容相同的标准审视其输出;
- 不得用 AI 生成的描述或摘要来替代对相关代码、issue 或讨论的实际理解。
Commit 钩子:用 pre-commit 在本地提前拦截 CI 失败
仓库包含 .pre-commit-config.yaml,定义了可在创建提交前后自动运行的钩子,用于在本地提前保证改动和提交信息能通过自动化 CI 检查。该文件要求 minimum_pre_commit_version: 3.2.0,并注册了两个 local 类型的钩子:
meta-lint-ci(stages:pre-commit):入口命令为bash Meta/lint-ci.sh,确保代码改动通过 lint;meta-lint-commit(stages:commit-msg):入口命令为 Meta/lint-commit.sh,对提交信息做 lint。
启用方式(先按 pre-commit 官方文档完成安装,然后二选一或同时执行):
# 安装 pre-commit 钩子:提交前运行 Meta/lint-ci.sh 等 lint 检查
pre-commit install
# 安装 commit-msg 钩子:校验提交信息格式
pre-commit install --hook-type commit-msg
lint-ci.sh 实际检查什么
从 Meta/lint-ci.sh 的源码可以看到,它是一组 lint 的聚合执行器,依次运行(任一失败则整体非零退出):
- Meta/Linters/check_debug_flags.sh、check_flatpak.py、check_html_doctype.py、check_idl_files.py、check_libweb_realm_mentions.py、check_newlines_at_eof.py、check_png_sizes.sh(前文所述 optipng 检查)、check_style.py、check_vcpkg.py;
- lint_executable_resources.sh、lint_prettier.sh、lint_python.sh、lint_shell_scripts.sh、lint_workflows.sh;
- lint_ipc.py:对 git 跟踪的所有
*.ipc文件做校验; - lint_clang_format.py:对 C++ 文件按项目强制版本执行 clang-format,并以
git diff --exit-code确认格式化后无差异; - Rust 侧检查:若改动涉及
*.rs、Cargo.toml、Cargo.lock、rust-toolchain.toml或rustfmt.toml,额外运行cargo fmt --check和cargo clippy -- -D clippy::all,否则直接输出 "No Rust files to check."。
值得一提的是 check_style.py 的具体规则,它把 CodePolicy 的诸多“Do”落实为机器可校验的检查:校验 #pragma once 必须存在且前后各有一个空行;校验 #include 只能是系统包含或可从当前文件直接解析的本地包含;禁止注释中出现 whatwg 单页 HTML 规范链接(加载过慢);要求 FIXME、AD-HOC、NB、NOTE 后必须紧跟冒号。这些细节解释了为什么代码风格在 Ladybird 中不是“建议”而是“门禁”。
另外从当前仓库结构看,.pre-commit-config.yaml 的钩子实际只调用了 Meta/lint-ci.sh,而 CodePolicy 文档中提到的 Meta/lint-ports.py 在当前树中已不存在,vcpkg 端口检查由 lint-ci.sh 内调用的 check_vcpkg.py 承担;以当前仓库的实际文件为准即可。
lint-commit.sh 逐条规则
Meta/lint-commit.sh 把提交信息格式规则变成了可执行的断言,遇到违规会打印完整提交信息并以非零退出。具体校验逻辑(源码逐行可对照):
| 规则 | 对应 lint 断言 |
|---|---|
| 只允许 Unix 风格 LF 换行 | 检测到 \x0D(CRLF)即失败 |
| 标题与正文之间必须有一个空行 | 第 2 行非空时报错 |
| 禁止 merge 提交(应使用 rebase) | 首行匹配 ^Merge branch 即失败 |
| 首行必须带类别前缀 | 首行须匹配 `^(Revert " |
| 类别后的第一个单词必须大写 | 首行须匹配 ^\S.*?: [A-Z0-9] |
| 主题行不得以句号结尾 | 首行以 . 结尾即失败 |
| 每行最长 72 字符 | 超长行失败,除非该行是独占一行的 URL(scheme://host/path 形式) |
禁止 Signed-off-by: 标签 |
正文含该行即失败 |
兼容 git commit --verbose 的 >8 截断线和 # 注释行,以及 fixup! 开头的超长前缀 |
这些行被跳过 |
对照 CodePolicy 的 Do 清单可以看出:72 字符换行、Category: 祈使句描述 格式、不以句号结尾,这三条“人读”的规则与钩子“机读”的断言是一一对应的——这就是“先本地钩子、再 CI”的双层门禁设计。
Git Notes:在 git log 中看到 PR 与评审信息
GitHub 上的 Ladybird 项目为每个提交维护了 git notes,内容例如该提交来自哪个 Pull Request、评审人信息等,且会自动更新。要在本地 git log 中看到 notes,需要一次性配置 fetch refspec(upstream 应替换为你本地克隆中对上游仓库的 remote 名,可用 git remote -v 查询):
git config --add remote.upstream.fetch '+refs/notes/*:refs/notes/*'
配置后,每次 git fetch 都会同步最新 notes。此后 git log 会在提交信息下方额外显示 Notes 段。以文档中给出的真实示例(提交 c1b0e180ba64d2ea7e815e2c2e93087ae9a26500)为例:
commit c1b0e180ba64d2ea7e815e2c2e93087ae9a26500
Author: Timothy Flynn <trflynn89@pm.me>
Date: Mon Jul 29 10:18:25 2024 -0400
LibWebView: Insert line numbers before each line in about:srcdoc
The behavior chosen here (fixed-width counters, alignment, etc.) matches
Firefox.
Notes:
Author: <提交作者的 GitHub 主页链接>
Commit: <该提交在上游 GitHub 仓库的链接>
Pull-request: <来源 Pull Request 的链接>
Reviewed-by: <评审人的 GitHub 主页链接> ✅
其中 Reviewed-by 行末尾的 ✅ 表示评审已完成。这一机制配合前文的提交信息类别规范(LibWebView: ...),使得提交历史在本地即可查看完整的溯源链:哪个库、哪个 PR、谁评审通过。
小结:策略文档与自动化门禁的闭环
Ladybird 的 CodePolicy.md 的价值在于它不只是一份“风格倡导”:文档中几乎每条硬性规则都能在当前仓库找到对应的自动化实现——72 字符与类别前缀由 Meta/lint-commit.sh 断言,clang-format 版本、#pragma once、SPDX 头、PNG 压缩由 Meta/lint-ci.sh 聚合的各 Meta/Linters/ 脚本校验,WPT 导入由 Meta/WPT.sh 承载。对研究该项目代码质量的读者而言,直接阅读这些脚本与本文对照,是理解其工程纪律最快、最可靠的路径。
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 StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00