Documentation
2026-09-06 12:20:35作者:丁柯新Fawn
The following block does not have a language attribute and should be ignored:
This is a fenced block without attributes, it's invalid and it should be ignored.
The following block should be given a js extension on extraction:
console.log("js");
The following block should be given a ts extension on extraction:
console.log("ts");
The following block is inside a markdown blockquote:
console.log("blockquote");
The following example contains the ignore attribute and will be ignored:
const value: Invalid = "ignored";
The following example will trigger the type-checker to fail:
const a: string = 42;
六个代码块对应的规则如下(后文会给出源码依据):
1. **无语言属性的块**(第 5–7 行):无效,直接忽略,不产生任何虚拟文件;
2. **` ```js `**(第 11–14 行):提取为 JavaScript 虚拟文件,期望命名 `main.md#11-14.js`;
3. **` ```ts `**(第 17–20 行):提取为 TypeScript 虚拟文件,期望命名 `main.md#17-20.ts`;
4. **引用块内的 `> ```ts `**(第 23–26 行):引用块前缀(每行行首的 `> `)会被剥除后照常提取,期望命名 `main.md#23-26.ts`;
5. **` ```ts ignore `**(第 29–31 行):`ignore` 属性使其完全跳过——注意块内故意写了 `const value: Invalid = "ignored";` 这样的错误类型,若不跳过就会污染类型检查;
6. **带类型错误的 ` ```ts `**(第 35–38 行):用于验证类型检查失败时的错误输出。
## 提取管线源码:从 Markdown 到虚拟文件
上述规则的实现在 [cli/util/extract.rs](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files) 中。入口函数 [extract_doc_tests](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L36-L38) 的文档注释说明了它的职责:
> Extracts doc tests from a given file, transforms them into pseudo test files by wrapping the content of the doc tests in a `Deno.test` call.
内部流程可以概括为四步:
### 1. 找出所有围栏代码块
[extract_files_from_fenced_blocks](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L121-L147) 对 Markdown 文本逐行扫描,由 [extract_markdown_fenced_blocks](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L157-L227) 完成围栏(fence)的配对。关键行为有两条,都可直接对照 main.md 验证:
- **围栏至少 3 个反引号**:[parse_markdown_fence_opening](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L235-L248) 中 `tick_count < 3` 时返回 `None`;闭合围栏的反引号数量必须**不少于**开围栏([is_markdown_fence_closing](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L250-L255));
- **HTML 注释内的块被跳过**:扫描器维护 `in_html_comment` 状态([L173-L189](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L173-L189)),`<!-- ... -->` 内部的代码块不会被提取。文档中可以用 HTML 注释隐藏不想被测试的示例。
### 2. 根据属性决定媒体类型
每个块的属性串交给 [media_type_from_attributes](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L388-L412) 解析:
- 属性包含 `ignore` → 返回 `None`,该块被丢弃(对应 main.md 第 29–31 行的行为);
- 第一个属性是 `js` / `javascript` / `mjs` / `cjs` / `jsx` / `ts` / `typescript` / `mts` / `cts` / `tsx` 之一 → 映射到对应的 `MediaType`;
- 无属性或属性不认识 → `MediaType::Unknown`,随后在 [extract_file_from_block](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L430-L432) 中直接 `return None`。
这就解释了 main.md 第一个块"没有语言属性、应被忽略"的原因:它不是被显式丢弃,而是根本无法推断出媒体类型。
### 3. 剥除引用块前缀
main.md 中那个引用块(第 23–26 行)是这段逻辑里最精细的部分。提取时需要把每行行首的 `> `(甚至嵌套的 `> > `)去掉,同时**不能误伤代码中真实的 `>` 字符**(比如模板字符串、泛型比较)。源码的处理方式是:
- 打开围栏时,[strip_markdown_fence_prefix](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L257-L267) 先剥掉缩进和任意个 `>` 标记,记录 `is_markdown_blockquote` 标志;
- 处理正文时,只有当块被标记为引用块,才用 [strip_markdown_blockquote_marker](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L514-L533) 循环剥离行首的 `>` 标记;非引用块保持原样,避免破坏代码里的字面 `>`。相关注释在 [extract_file_from_block 内 L447-L464](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L447-L464) 中有明确说明。仓库里还有一个专门针对引用块的规格测试 [tests/specs/test/doc_blockquote/main.ts](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/tests/specs/test/doc_blockquote/main.ts?utm_source=gitcode_repo_files) 做补充覆盖。
### 4. 生成带行号的虚拟文件标识
提取出的每个块都会得到一个带行范围片段的 URI,格式为 `<原文件>#<起始行>-<结束行>`([extract_file_from_block L475-L494](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L475-L494)),再追加扩展名得到 `main.md#11-14.js` 这样的虚拟文件路径。媒体类型则通过 content-type 头携带([L499-L503](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L499-L503)),因为路径片段本身不用于推断媒体类型。这与 [main.out](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/tests/specs/test/markdown/main.out?utm_source=gitcode_repo_files) 中出现的 `Check [WILDCARD]main.md#11-14.js` 完全对应。
## 从提取到执行:Deno.test 包装与权限
提取只是第一步。[extract_doc_tests](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L36-L38) 与 [extract_snippet_files](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L49-L51) 的区别在于包装方式:前者会把每个提取出的代码块包进一个 `Deno.test` 调用(`WrapKind::DenoTest`),后者不包装。
[generate_pseudo_file](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L958) 及其上方的文档注释([L857-L957](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L857-L957))描述了伪文件生成的完整变换:
1. **注入基础文件的导出**:若 Markdown 所在模块(或同一文件的 TS 代码部分)有 `export` 声明,会用 `ExportCollector`([L603-L635](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L603-L635))收集具名导出与默认导出,自动为文档测试注入 `import` 语句——文档示例因此可以直接使用所在模块导出的 API,无需手写导入;
2. **避免重复标识符**:如果代码块自己已经 import 了同名符号,自动注入会被跳过(文档注释中的 Edge case 1);
3. **剥掉 `export` 关键字**:`export const x = ...` 不能出现在 `Deno.test` 回调内部,AST 变换会把 `ExportDecl` 降级为普通声明([Transform L1032-L1076](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L1032-L1076),对应文档注释 Edge case 2);
4. **包装成测试**:[wrap_in_deno_test](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L1171-L1253) 生成形如 `Deno.test("<虚拟文件 URI>", async () => { ... })` 的语句,测试名就是虚拟文件路径,这也是测试输出中能看到 `main.md#35-38.ts` 定位的来源。
此外还有一个与 main.md 无直接关系但属于同一管线的能力:**shebang 权限声明**。若代码块第一行是 `#!/usr/bin/env -S deno run --allow-read`,[parse_shebang](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L554-L586) 会用 Deno 自己的 CLI 参数解析器解析这些标志,并把权限转发为 `Deno.test` 的 `{ permissions: ... }` 选项([permissions_options_object L1263-L1277](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L1263-L1277):`--allow-all` 变为 `{ permissions: "inherit" }`,无权限标志变为 `{ permissions: "none" }`)。源码注释同时列出了已知限制:按文件名匹配 `deno`、作用域 `--deny-*=<path>` 与 `--ignore-*` 尚不支持——不可解析的 shebang 会令生成的测试主动抛出 `invalid doc test hashbang` 错误([wrap_in_deno_test L1182-L1203](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/cli/util/extract.rs?utm_source=gitcode_repo_files#L1182-L1203))。
## 预期输出:类型错误如何被定位
运行 `test --doc --allow-all main.md` 后,[main.out](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/tests/specs/test/markdown/main.out?utm_source=gitcode_repo_files) 给出了完整快照:
```text
Check [WILDCARD]main.md#11-14.js
Check [WILDCARD]main.md#17-20.ts
Check [WILDCARD]main.md#23-26.ts
Check [WILDCARD]main.md#35-38.ts
TS2322 [ERROR]: Type 'number' is not assignable to type 'string'.
const a: string = 42;
^
at [WILDCARD]/main.md#35-38.ts:2:11
error: Type checking failed.
info: The program failed type-checking, but it still might work correctly.
hint: Re-run with --no-check to skip type-checking.
把它与 main.md 逐行对照,可以验证全部规则:
- 恰好 4 个
Check行——无属性块(第 5–7 行)与ignore块(第 29–31 行)都没有出现在类型检查里,否则第 30 行的const value: Invalid也会报未定义类型错误; - 四个虚拟文件的行范围(
#11-14、#17-20、#23-26、#35-38)与 main.md 中围栏的起止行完全一致,其中#23-26证明引用块内的代码同样参与检查; - 唯一的类型错误
TS2322精确落在main.md#35-38.ts:2:11,即第 6 个代码块第 2 行const a: string = 42;的42处——虚拟文件行号从块内首行计 1,列号指向赋值右侧; - 退出码为 1(对应 test.jsonc 的
"exitCode": 1),末尾给出--no-check的提示。
延伸:同一管线的其他入口
- JSDoc 注释提取:extract_files_from_source_comments 用正则从 JSDoc 块注释中挑出
```围栏,与 Markdown 走同一个extract_file_from_block,因此语言属性、ignore、引用块剥除、shebang 规则完全一致; deno doc对 Markdown 的支持:仓库另有 tests/specs/doc/markdown 规格(含 README.md 与 expected.out),验证deno doc能从 Markdown 文件输出 API 文档;- JSR 发布物中的文档测试:tests/specs/doc/markdown_jsr 验证从 JSR 包中提取并测试文档代码块的路径。
小结
以 main.md 为例,deno test --doc 的行为可以归纳为一条清晰的规则表:
| 代码块写法 | 行为 | 源码依据 |
|---|---|---|
无语言属性的 ``` |
忽略(媒体类型 Unknown) | extract.rs#L388-L412、#L430-L432 |
```js / ` |
登录后查看全文
热门项目推荐
相关项目推荐
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 StartedRust0624
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
项目优选
收起
deepin linux kernel
C
33
18
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
856
1.35 K
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
590
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.73 K
暂无描述
Markdown
897
5.79 K
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.56 K
1.01 K
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1 K
512
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
911
1.83 K
openGauss kernel ~ openGauss is an open source relational database management system
C++
213
313