首页
/ Documentation

Documentation

2026-09-06 12:33:09作者:殷蕙予

This test case checks if @ts-expect-error comment works as expected.

// @ts-expect-error
const a: string = 42;

这段代码块的技术要点在于 `@ts-expect-error` 注释的语义(TypeScript 标准行为,Deno 的本地类型检查遵循同样规则):

1. 注释**必须紧邻错误行上方**(本例中直接位于 `const a: string = 42;` 上一行),其作用是抑制下一行代码的类型错误;
2. 如果下一行实际上没有类型错误,`@ts-expect-error` 本身会触发 `Unused '@ts-expect-error' directive` 报错——因此该注释同时是一个“双向断言”:既声明此处应当出错,又防止错误消失后无人察觉;
3. `const a: string = 42;` 是刻意的类型错误(`number` 不能赋给 `string`),有注释在时检查通过;若删除注释,`deno test --doc` 会在类型检查阶段失败。

这正是该用例的验证目标:doc 测试提取出的代码块在送入类型检查器时,`@ts-expect-error` 注释必须被原样保留并生效。若提取过程丢弃了注释(早期版本曾出现过类似问题),类型检查将直接报 `Type 'number' is not assignable to type 'string'`。

## 用例的驱动方式:`__test__.jsonc` 规格声明

同目录下的 [__test__.jsonc](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/tests/specs/test/markdown_ts_expect_error/__test__.jsonc?utm_source=gitcode_repo_files) 是该规格测试的执行声明:

```jsonc
{
  "args": "test --doc main.md",
  "exitCode": 0,
  "output": "main.out"
}

含义拆解:

  • args: "test --doc main.md":等价于在该目录执行 deno test --doc main.md--doc 标志告诉 Deno 把目标文件当作文档处理——扫描其中所有 ts / typescript 围栏代码块,将每个代码块包装成 Deno.test 调用,并默认对提取出的本地代码执行类型检查;
  • exitCode: 0:类型检查通过(@ts-expect-error 生效)且测试运行时无异常,进程必须以 0 退出。由于代码块本身不含 Deno.test 调用,--doc 会为其自动生成测试包装器,代码块中的语句在测试体中执行;
  • output: "main.out":声明期望的 stdout/stderr 对照基准,供 Deno 的 specs 集成测试框架比对输出。

Deno 的 specs 测试框架(由 tests/specs 下各目录驱动)会在真实构建出的 Deno 二进制上回放这些声明,因此这条用例同时是对 --doc 抽取器、类型检查器和测试运行器三者的端到端回归保护。

源码级原理:代码块如何被抽成独立可检查模块

Deno 的文档代码提取逻辑位于 cli/util/extract.rs。该模块的核心工作可以概括为三步:

  1. 扫描围栏代码块:对 Markdown(以及 JSDoc 注释块、HTML 注释等载体)识别出语言标记为 ts/typescript 的代码块,并记录起止行号;
  2. 生成带行号定位的虚拟模块:每个代码块得到一个 file:///<原路径>#起-止.ts 形式的 specifier(例如 file:///main.md#4-11.ts),媒体类型为 TypeScript。这个命名让类型错误信息可以精确指回源文件中的行区间;
  3. 包装为测试:把代码块内容包进 Deno.test("<specifier>", async () => { ... }) 中,并按需自动导入源文件里的顶层导出(从源码结构看,抽取器会分析块内标识符与源文件导出的对应关系生成 import 语句,使文档示例可以直接引用同文件的函数而不必手写 import)。

extract.rs 内嵌的单元测试本身就包含与本文主题直接相关的回归用例(对应上游 issue #26728,见 extract.rs 第 1819–1845 行):

/**
 * ```ts
 * // @ts-expect-error: can only add numbers
 * add('1', '2');
 * ```
 */
export function add(first: number, second: number) {
  return first + second;
}

期望生成的测试源码为:

import { add } from "file:///main.ts";
Deno.test("file:///main.ts#3-7.ts", async ()=>{
    // @ts-expect-error: can only add numbers
    add('1', '2');
});

可以清楚看到:@ts-expect-error 注释在“源码 → 生成模块”的转换中被逐字保留,测试包装不会剥离任何行。同一文件中针对 Markdown 输入的测试用例(extract.rs 第 1846–1871 行)则验证了 .md 文件中代码块被抽取为 file:///main.md#4-11.ts 的完整路径——这正是 markdown_ts_expect_error 规格用例所依赖的机制。

姊妹用例:JSDoc 注释中的同名验证

仓库中存在一个结构平行的用例 doc_ts_expect_error/mod.ts,它验证 JSDoc 注释块(而非 Markdown 文件)中的 @ts-expect-error

/**
 * ```ts
 * import { add } from "./mod.ts";
 *
 * add(1, 2);
 *
 * // @ts-expect-error: can only add numbers
 * add('1', '2');
 * ```
 */
export function add(first: number, second: number) {
  return first + second;
}

两者共同守住的约束是一致的:无论文档载体是 .md 还是 .ts 的 JSDoc,doc 测试抽取后代码块中的注释必须完整保留,且带 --doc 的运行默认执行类型检查,从而让 @ts-expect-error 的“此处必须出错”断言在文档语境下同样成立。

如何在本地复现与延伸

在仓库中进入用例目录即可复现(只读参考):

cd tests/specs/test/markdown_ts_expect_error
deno test --doc main.md
登录后查看全文
热门项目推荐
相关项目推荐