Astro 源码注释规范:面向贡献者的 JSDoc 契约、行内注释与"删除测试"
Astro 仓库维护了一套严格的源码注释规范,它不是给最终用户看的 API 文档,而是写给"数月乃至数年后、拿着仓库 HEAD 版本、却完全没有你当前上下文"的贡献者读的。读完本篇,你将掌握 Astro 贡献者注释的三层分工(模块概述 / 条目文档 / 行内注释)、"删除测试"判据、变通方案(workaround)必须附 issue 链接的硬性要求、七类被明令禁止的注释反模式,以及 @deprecated、TODO: remove in Astro N 等仓库专属惯例,并在需要向 Astro 提交 PR 时写出符合社区评审标准的注释。
适用边界:贡献者注释 vs 用户文档
这条规范首先划清了一条容易踩线的边界:它只管贡献者面向的 .ts/.js 源码注释,不适用于面向最终用户的文档。仓库中有三类"注释"不在此管辖范围内,编辑它们需要走完全不同的流程:
@docs标签的 JSDoc 块——位于 packages/astro/src/types/public/config.ts 和 packages/astro/src/core/errors/errors-data.ts 中,会被外部docgen工具抓取并发布到 Astro 官方文档站。编写这类内容需遵循 packages/astro/src/core/errors/README.md 中的错误信息写作指南,并且必须经过文档团队(docs team)评审——CI 会在types/public/**变动时重新生成参考文档。types/public/**下的其他 JSDoc——它们通过编辑器的 IntelliSense 直接暴露给用户。要站在"Astro 用户正在建站"的视角写作,而不是"贡献者正在读源码"的视角。- 除此之外,规范正文讨论的所有内容,都针对贡献者在 HEAD 上读取的源码。
仓库中的真实 @docs 样例
config.ts 中每个配置项都带有结构化 JSDoc 块,包含 @name、@type、@default、@version、@description 等标签,例如 config.ts 中的:
/**
* @name server.port
* @type {number}
* @default `4321`
* @description
* Set which port the dev server should listen on.
*
* If the given port is already in use, Astro will automatically try the next available port.
*/
port?: number;
这些块的用户是"配置 astro.config.mjs 的建站者",描述的是配置契约本身,绝不允许夹带内部实现说明(比如某个内部归一化函数的行为细节)——这正是该技能文档边界章节反复强调的反面案例。
errors-data.ts 的头部注释本身就是一条边界声明,见 errors-data.ts:
// BEFORE ADDING AN ERROR: Please look at the README.md in this folder for general guidelines on writing error messages
// Additionally, this code, much like `types/public/config.ts`, is used to generate documentation, so make sure to pass
// your changes by our wonderful docs team before merging!
文件内每条错误都按 README.md 的规范用 @docs、@message、@see、@description 标签组织,例如 UnknownCompilerError(errors-data.ts)就是一个完整样例:@description 从用户视角解释发生了什么、为什么、该怎么做,而不是描述抛错逻辑的内部实现。
读者的定义:一个只有 HEAD 的陌生人
规范把目标读者定义得非常具体:一位精通 TypeScript、但完全没有你当前上下文的 Astro 贡献者——他看不到你这轮对话、看不到你的 PR、看不到关联 issue、也看不到你的 diff,他只能看到仓库 HEAD 这一份代码。
由这个前提直接推出两条铁律:
- 永远不要叙述变更历史。 "now"(现在)、"previously"(以前)、"no longer"(不再)、"the new approach"(新方案)这类词在 HEAD 上毫无意义——那里只存在一种做法。注释要陈述代码如何工作,而不是它如何演变而来。唯一的例外是
@deprecated声明(见后文"本仓库的惯例"),因为它描述的是契约的未来,而读者确实需要知道。 - 永远不要对评审者说话。 注释不是用来论证"我的改动是正确的"——"this properly handles X"(这正确处理了 X)这类辩解属于 PR 描述,不属于源码。注释必须为代码现状提供永久性辩护,而不是为你的改动辩护。
三类注释,三件不同的事
| 类型 | 语法 | 职责 | 内容 |
|---|---|---|---|
| 文件 / 模块概述 | 文件顶部的 /** */ |
解释(Explanation) | 模块为什么存在、它定义的概念和术语、各部分如何关联、设计动因 |
| 条目文档(Item docs) | 直接位于声明上方的 /** */ |
参考(Reference) | 契约:行为、参数、返回值、抛出的错误、不变量。中性、事实性 |
| 行内注释 | 函数体内的 // |
动机(Rationale) | 只有代码自己说不出来的东西:约束、带 issue 链接的变通方案、不明显的耦合 |
这三类职责严格分离,对应 Diátaxis 文档框架中"解释 / 参考 / 动机"的三分法(技能文档在 References 一节明确以此为理论来源)。两个典型的混淆方向被点名禁止:
- 实现细节不属于
/** */契约——应下沉为函数体内的//注释; - 契约不应散落在行内注释里——应挂在声明上方的文档块上。
行为文档:为人类读者写契约,而非翻译实现
当一条声明确实需要条目文档时,写作目标是给人类读者描述契约,而不是把实现逐行翻译成文字。规范同时强调这并不意味着"每个函数都要写 JSDoc"——名称、类型和结构本身应当能承载直观行为,承载不了就先改名字。
具体写作要求:
- 先用一句通俗语言说明函数返回什么、完成了什么;
- 使用短句或中句,每句只承载一个主要思想;
- 调用方不需要时,避免内部术语(Astro 黑话)。确需使用技术术语,就在同一段里解释;
- 描述调用方可见的、可能令人意外的注意点:回退行为、工作量的上限、含糊的结果、重载的匹配顺序、副作用,以及返回
undefined、null、空结果或其他不确定值的条件; - 当调用方需要区分或恢复时,文档化抛出的错误;
- 除非调用方理解行为或安全使用 API 必须,否则不描述实现细节。
何时必须加示例
当行为依赖于签名无法清晰表达的关系时,应添加 @example。Astro 与 TypeScript 中最常见的五类场景:
- 哪个重载会被选中;
- 参数如何映射到可选参数或 rest 参数;
- 哪个公开的 Astro 入口点暴露了实现在别处的符号(re-export 场景);
- 含糊路由、不完整配置或缺失内容时的回退行为;
- 含义无法从类型直接看出的返回值。
示例的写法也有规范:先用散文引出代码块,说明它演示什么、预期结果是什么;代码片段保持最小、自包含,并且站在 Astro 用户或拥有该契约的内部调用方的视角书写。
模块文档:描述持久的概念
模块级 /** */ 应当描述一个持久的概念、架构边界或设计理由。禁止罗列文件中的各个导出项来"总结"文件——随着符号被增删改名,这类清单很快就会过时(stale)。如果一个模块没有值得解释的持久概念,那就写一行简短描述,或者干脆不写概述。
删除测试(The Deletion Test)
写任何注释之前先问自己:这条注释陈述的信息,读者能否从代码本身恢复出来?
- 如果信息已经被名称、类型或结构承载,就不要写注释;如果名字承载不了,去改进名字。
- 真正有资格获得注释的信息:一个不变量、一个动机、与别处代码的耦合、带链接的变通方案、某个依赖令人意外的行为、以及该模块定义的专业术语。
编辑旧代码时同一测试反向适用:不再通过的注释应当删除,而不是留着慢慢腐烂。技能文档把这句收在自检清单的末尾,并给出总基调:"删除是默认选项;缺失的注释比误导性的注释更便宜。"
变通方案必须链接 issue 或 PR
这是本仓库一条高度一致、可被机器核验的硬性规则:**任何解释变通方案(workaround)、HACK、回归防护(regression guard)或依赖意外行为的注释,必须链接到动机所在的 GitHub issue 或 PR。**链接的存在让未来的读者能判断这个变通方案是否仍然必要——技能文档的原话是"没有链接的变通方案与一个错误无法区分"。
规范给出的标准示例:
// Handle recommended nanostores. Only @nanostores/preact is required from our testing!
// Full explanation and related bug report: https://github.com/withastro/astro/pull/3667
'@nanostores/preact',
这条规则在仓库中并非纸上谈兵。packages/astro/src/vite-plugin-environment/index.ts 里的 ALWAYS_NOEXTERNAL 列表就是规范风格的活体样本,每个条目都是"一行理由 + issue/PR 编号":
const ALWAYS_NOEXTERNAL = [
// This is only because Vite's native ESM doesn't resolve "exports" correctly.
'astro',
// Vite fails on nested `.astro` imports without bundling
'astro/components',
// Handle recommended nanostores. Only @nanostores/preact is required from our testing!
// Full explanation and related bug report: https://github.com/withastro/astro/pull/3667
'@nanostores/preact',
// Must be bundled so the prerender output resolves Astro's own copy, not an
// older hoisted version from another dependency. See https://github.com/withastro/astro/issues/17508
'neotraverse',
];
注意其中的措辞都是现在时动机("Vite fails on..."、"Must be bundled so..."),而不是"我们改了它因为……"式的历史叙述——恰好同时满足"读者铁律"和"变通链接"两条规则。
七类被禁止的注释模式
技能文档逐一点名并给出了改写对照,这是全篇最具操作性的部分:
1. 复述下一行代码。 看到就删:
// Increment the generation counter
generation += 1;
2. 叙述变更历史。 改写为现在时的动机说明:
// BAD: We now resolve lightningcss from the user's root instead of ours.
// GOOD: lightningcss is an optional peer dep, so it resolves from the user's project root.
3. 对评审者的辩解。 论据移回 PR 描述:
// BAD: This correctly handles the multi-encoded path from the bug report.
// GOOD: A path still encoded after MAX_DECODE_ITERATIONS is rejected, so
// middleware and routing can never disagree on the decoded path.
4. 换了个说法的 JSDoc。 复述声明名的文档块等于没说:
// BAD:
/** Compiles the styles. */
function compileStyles(...)
// GOOD:
/** Rewrites relative `url()` references in `css` against `base`, leaving
* absolute and data URLs untouched. */
function compileSkills(...)
(注意示例中 GOOD 版本说明了行为契约——重写相对 url()、不动绝对与 data URL——而不是翻译函数名。)
5. 含糊的套话。 "some cases"、"various reasons"、"handles edge cases"、"etc."——要么点名具体是什么,要么删掉整句。
6. Emoji。 源码中全面禁用,注释也不例外(这是仓库级政策)。
7. 临时分区横幅(如 // ----- helpers -----、// ==== TYPES ====)。本仓库没有 // #region 折叠惯例,不要添加横幅。如果一个文件长到你伸手去找横幅,那是该拆分文件的信号,不是该装饰它的信号。
本仓库的注释惯例
以下惯例是 Astro 源码特有的约定,脱离仓库语境可能不成立:
JSDoc 标签。 @param name - description、@returns、@throws 用于陈述契约;只有当签名本身有歧义时才用花括号包裹类型(如 @returns {Promise<string>});非显而易见的用法配 @example 加 ```js 围栏代码块。
交叉引用。 使用 {@link Symbol} / {@linkcode Symbol} 而不是裸写符号名,这样符号改名时引用会自动更新,编辑器也能跳转到目标。
@internal。 标记不属于公开 API 面的符号。它只是约定——这个仓库没有 typedoc 或 api-extractor 来剥离它——所以它表达的是意图,不能替代真正的访问控制手段。
@deprecated。 先说迁移方案,再说移除时间点,仓库示例:
/** @deprecated Use the instance method `cookies.consume()` instead. This will be removed in Astro 7 */
关键是说"替代方案是什么",而不只是"此符号已废弃"。这条面向未来的陈述属于读者需要的契约信息,不属于被禁止的历史叙述——这正是"读者铁律第 1 条"预留的例外通道。
TODO。 延后工作用 // TODO:;有 issue 跟踪就附链接;受破坏性变更窗口约束的工作使用既定句式 // TODO: remove in Astro <N>。本仓库不存在 FIXME——不要引入它。这句约定在源码中有直接证据,例如 packages/astro/src/content/runtime.ts 中的两处:
// TODO: remove in Astro 8
warnForPropertyAccess(
logger,
result.data,
'slug',
`[content] Attempted to access deprecated property on "${collection}" entry.\nThe "slug" property is no longer automatically added to entries. Please use the "id" property instead.`,
);
这里的措辞细节值得品味:TODO 本体只说"在 Astro 8 移除",而给用户看的警告字符串才解释替代路径——两条信息各归其位,互不污染。
编辑已有代码时的三条纪律
- 保留既有注释。 如果你的改动改变了行为,就扩展或修正对应的那段散文——绝不替换成通用套话。删除来之不易的上下文,比留一条略微过时的注释更糟。
- 注释被你的改动说假了,就在同一个 diff 里修好。 过时的注释比没有注释更坏。
- 匹配周边的注释密度。 文档密集的模块,新增条目应达到同等水准;也不要给本来就稀疏的模块泼一身注释。
收尾自检:只重读 diff 里的注释
技能文档要求:完成任何触碰注释的任务后,把 diff 中新增的注释单独摘出来、脱离代码改动重读一遍,逐条过四问:
- 每一条是否通过删除测试?
- 是否有引用对话、引用本次改动本身、或对评审者说话的?
- 每个变通方案是否链接了它的 issue 或 PR?
- 一个看不到 diff 的读者能否独立理解每一条?
不达标的,修掉或删掉。删除是默认选项。
自动化评估:规则如何被验证
这些规范并非仅靠人工评审执行。仓库中 .agents/skills/writing-comments/evals/evals.json 为"写作注释"技能定义了三个可断言的评估场景,恰好覆盖规范的核心分支:
- 变通场景:给定一段"无上下文"的
resolveLightningcss源码,要求补注释。断言包括:注释必须位于函数体内(//)而非声明 JSDoc、必须说明"lightningcss 是可选 peer dependency、须从用户项目根解析"、必须包含追踪 issue 的准确 URL、禁止出现叙述 import/return 的行、禁止now/previously/correctly handles等历史或辩解措辞、禁止 emoji 与横幅。 - 契约场景:给定
resolveEntry(collection, slug)(精确匹配 → 回退 →undefined的语义无法从签名看出),要求补写完整条目 JSDoc。断言包括:开头散文描述调用方可见的结果而非复述函数名、JSDoc 覆盖精确匹配/回退/返回undefined三种条件、包含@param collection -、@param slug -、@returns、带js围栏代码块的@example、且不得宣称函数会抛错、不得谈论Map.get等实现细节。 - 边界场景:要求把一段
normalizeAssets()的内部实现动机写进types/public/config.ts的@docs块——正确行为是拒绝修改,识别出@docs块属于生成的用户文档、不在贡献者注释规则管辖内,且需要遵循 errors/README.md 一类的仓库指引并取得 docs 团队评审。
这三个场景与本技能规范文档(.agents/skills/writing-comments/SKILL.md)构成"规范—验证"闭环:凡你在 Astro 中写下的注释,都可以用同样的断言清单自测。
参考
- 规范本体:.agents/skills/writing-comments/SKILL.md;评估用例:evals.json
- 用户文档边界:packages/astro/src/core/errors/README.md、packages/astro/src/types/public/config.ts、packages/astro/src/core/errors/errors-data.ts
- 规范风格实证:packages/astro/src/vite-plugin-environment/index.ts、packages/astro/src/content/runtime.ts
- 理论来源(技能文档 References 一节引用):Diátaxis 框架(解释 / 参考 / 动机三分法)、TSDoc 标签规范
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 StartedRust0623
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