Deno 源码仓库的开发者工具链详解:tools/format.js、lint.js、wgpu_sync.js 与 copyright_checker.js 的原理与用法
本文基于 Deno 仓库的 tools/README.md 展开,系统讲解 Deno 官方源码仓库 tools/ 目录下的四类核心开发工具脚本:代码格式化(format.js)、代码静态检查(lint.js)、WebGPU 上游同步(wgpu_sync.js)与版权头校验(copyright_checker.js)。结合各脚本的源码实现,你将理解这些脚本在 CI 前的准入流程中扮演什么角色、底层如何并行调度 dprint / deno lint / clippy,以及如何安全地执行上游 vendor 同步与版权合规检查。
tools/ 目录定位:Deno 开发的准入检查层
tools/README.md 开篇一句话说明了该目录的定位:
Documentation for various tooling in support of Deno development.
即 tools/ 目录下的脚本共同服务于 Deno 自身的开发流程,其中 format.js 与 lint.js 被明确标注为 代码提交前必须执行的前置步骤("It is a prerequisite to run this before code check in")。这与仓库根目录的 CLAUDE.md 中的开发约定相互印证:
- Before committing, make sure `tools/format.js` is run to format your code
- ...`tools/lint.js --js` and fix any lint errors before committing
- If you changed Rust code, make sure to run `tools/lint.js` and fix any lint
也就是说,对 Deno 贡献代码的标准流程是:先 format,再 lint,全部通过后才提交。下面逐个脚本展开。
format.js:统一入口的代码格式化
基本用法
按 tools/README.md 的说明,format.js 使用 dprint(Rust 代码侧使用 rustfmt)来格式化代码库,运行方式为:
deno run --allow-read --allow-write --allow-run ./tools/format.js
源码实现:dprint 统一配置
阅读 tools/format.js 可以发现,脚本逻辑非常短,核心是拼装并执行一条 dprint 命令:
const subcommand = Deno.args.includes("--check") ? "check" : "fmt";
const configFile = join(ROOT_PATH, ".dprint.json");
const cmd = new Deno.Command("deno", {
args: [
"run",
"-A",
"--no-config",
"npm:dprint@0.47.2",
subcommand,
"--config=" + configFile,
],
cwd: ROOT_PATH,
stdout: "inherit",
stderr: "inherit",
});
从源码结构看有三个值得注意的设计点:
- dprint 版本锁定:通过
npm:dprint@0.47.2精确固定 dprint 版本,保证所有开发者与 CI 看到一致的格式化结果,避免“在我机器上是好的”式漂移。 --check子命令:脚本解析Deno.args,当传入--check时向 dprint 转发check子命令(只检测不修改),否则执行fmt。这意味着你可以用deno run ./tools/format.js --check做“格式是否干净”的快速校验,适合在提交前使用。- 统一配置源:所有格式规则收敛在仓库根的 .dprint.json 中,脚本本身不包含任何格式规则,规则变更只需改一个配置文件。
- 退出码透传:
Deno.exit(code)将 dprint 的退出码原样传出,因此该脚本可以直接作为 CI 任务或 Git hook 的判定依据。
lint.js:JS 与 Rust 双栈的静态检查调度器
基本用法
tools/README.md 说明 lint.js 使用 deno lint 与 clippy 检查代码库,同样是提交前必跑步骤:
deno run --allow-read --allow-write --allow-run ./tools/lint.js
文档还给出了一条实用技巧:可以用 cargo 运行当前正在构建(或已构建)的本地 deno 可执行文件来执行这些工具脚本,从而让工具链检查的是你正在开发的那个 Deno 版本,而不是 PATH 里安装的版本:
cargo run -- run --allow-read --allow-write --allow-run ./tools/<script>
这条建议对 Deno 内核贡献者尤其重要——比如验证新的 lint 规则时,需要用包含该规则的那个构建去跑 tools/lint.js。
参数与并行调度模型
tools/lint.js 支持 --js 与 --rs 参数控制检查范围,两个都不传则 JS 与 Rust 都检查:
let js = Deno.args.includes("--js");
let rs = Deno.args.includes("--rs");
if (!js && !rs) {
js = true;
rs = true;
}
各检查项被推入 promises 数组后用 Promise.allSettled(promises) 并行执行,任一失败则打印错误并以退出码 1 结束。JS 侧包含 6 类检查,Rust 侧包含 3 类,其中 checkCopyright(版权检查)在同时启用 --js 与 --rs 时才跑。这种设计让 tools/lint.js --js 只查 JS 侧、--rs 只查 Rust 侧,方便在 CI 矩阵中拆分任务。
clippy 侧:deny 清单与分 crate 策略
clippy() 函数是 lint.js 中最复杂的部分。它首先定义了一组拒绝级 lint 规则:
const clippyDenyFlags = [
"--",
"-D", "warnings",
"--deny", "clippy::unused_async",
"--deny", "clippy::print_stderr",
"--deny", "clippy::print_stdout",
"--deny", "clippy::large_futures",
"--deny", "clippy::allow_attributes_without_reason",
];
源码注释解释了为什么禁用 print_stdout / print_stderr:Rust 标准输出宏在管道断开时(例如 deno test | head)会 panic,因此 Deno 代码库要求用 log crate 打诊断日志、用 deno_print 的 drop_println! 宏打 stdout。更细致的是,脚本会实时捕获 clippy 的 stderr 流,一旦匹配到 clippy::print[-_]std(out|err) 字样就额外打印这条提示,把“报错原因 + 修复方向”直接喂给开发者。
在 workspace 层面,由于 Cargo 的 --all-features 无法表达互斥的引擎后端,脚本通过 cargo metadata --no-deps 解析出所有 workspace 成员的全部 feature(剔除 QuickJS 与平台相关 V8 模式),显式拼接 --features 传给 clippy,并 --exclude deno_core;deno_core 则单独按固定 feature 集(default,unsafe_runtime_options,unsafe_use_unprotected_platform,v8)再跑一次 clippy。这种“分 crate、分 feature”的策略保证每个 crate 都在其真实的 feature 组合下被检查。
此外 clippy 检查还会遵循 buildMode()(来自 tools/util.js):传入 --release 时 clippy 也加 --release,使检查目标与实际构建模式一致。
几个高信息量的内置约定检查
除 deno lint 与 clippy 外,lint.js 还内嵌了几个纯 JS 实现的“仓库宪法”检查:
ensureNoNonPermissionCapitalLetterShortFlags:正则扫描 libs/cli_parser/src/defs.rs 中所有.short('X')声明,断言大写短选项只能是权限相关标志(A/E/I/N/R/S/W等),并维护一份白名单。源码注释说明了动机:大写短选项只关联权限,是为了让用户审查命令时能“多一分警惕”。ensureDisallowedMethodsEnforced:强制ext/、libs/、runtime/下每个 crate 都有clippy.toml,且其中必须列出禁止直接调用的方法清单(如std::fs::read、std::fs::write、std::path::Path::canonicalize、url::Url::to_file_path等),libs 类 crate 还要额外禁止std::env::var、std::time::SystemTime::now等。这从静态层面保证 Deno 扩展必须走 ops 抽象访问文件系统/环境变量/时间,而非直接调用 std。ensureNoUnusedOutFiles:遍历tests/specs/下所有__test__.jsonc,把其中output字段引用的.out文件从集合中删除,剩下的即为“无人引用的 .out 文件”,报错要求清理——防止测试快照膨胀。ensureNoNewTopLevelEntries:通过gitLsFiles列出仓库顶层条目,与白名单(.cargo、cli、doc、ext、libs、runtime、tests、tools、x及各根配置文件)比对,新增任何顶层目录或文件都会被拒绝,注释明确写着“Keep the root of the repository clean”,且向白名单添加条目必须先讨论。ensureWorkflowYmlsUpToDate:逐个运行.github/workflows/*.ts生成器(带--lint),确保所有.generated.yml工作流文件与其 TypeScript 源同步。
文件收集层的 Git / Jujutsu 双兼容
上述检查都依赖 tools/util.js 中的 getSources(baseDir, patterns) 收集文件。值得留意的是 gitLsFiles 的实现:如果检测到 .jj 目录(即仓库用 Jujutsu 管理),会把 git pathspec 翻译成 jj 的 glob: 文件集(包括把 *.ext 映射成 **/*.ext、把结尾 / 的目录映射为 ** 等归一化逻辑),否则走 git ls-files -z --exclude-standard --cached --modified --others。这让工具脚本在 git 与 jj 工作流之间无缝切换,也说明 Deno 团队正在并行试验版本控制系统。
wgpu_sync.js:把 gfx-rs/wgpu 的 deno_webgpu 树 vendor 进来
tools/README.md 对 wgpu_sync.js 的说明是:
wgpu_sync.jsstreamlines updatingdeno_webgpufrom gfx-rs/wgpu. It essentially vendors thedeno_webgputree with a few minor patches applied on top, somewhat similar togit subtree.
即该脚本从上游 gfx-rs/wgpu 仓库整树拷贝(vendor)deno_webgpu 到本仓库 ext/webgpu/ 目录,再打少量补丁,类似 git subtree 的语义。官方推荐的操作流程为四步:
- 更新 tools/wgpu_sync.js 中的
COMMIT或V_WGPU; - 运行
./tools/wgpu_sync.js; - 仔细复查改动,必要时手工打补丁;
- 提交并发送 PR。
源码走读:同步流水线
tools/wgpu_sync.js 的头部定义了同步的两个关键变量:
const COMMIT = "ae87ffe28041a7ebd82d8d3c2fa0e2343f0f0234";
const REPO = "gfx-rs/wgpu";
const V_WGPU = "29.0.1";
const TARGET_DIR = join(ROOT_PATH, "ext", "webgpu");
main() 按固定顺序执行四个阶段:
await main(); // => clearTargetDir → checkoutUpstream → patchCargo → patchReadme → format.js
clearTargetDir:rm -r ext/webgpu/*,清空本地 vendor 目录,保证每次同步从干净状态开始(这也是 README 要求“double check changes”的原因——上游文件被整体替换)。checkoutUpstream:通过 GitHub API 拉取指定COMMIT的 tarball,再用tar --strip=2只解压其中的deno_webgpu/子树到ext/webgpu/:注意这一步依赖网络与 bash,因此脚本 shebang 声明了curl -L https://api.github.com/repos/gfx-rs/wgpu/tarball/${COMMIT} | \ tar -C 'ext/webgpu' -xzvf - --strip=2 'gfx-rs-wgpu-ae87ffe/deno_webgpu/'--allow-read --allow-write --allow-run。patchCargo:对 vendor 进来的ext/webgpu/Cargo.toml做正则替换,把version对齐到根 Cargo.toml 中登记的deno_webgpu版本,把authors/license/repository归并到 workspace 继承(authors.workspace = true等);同时把根 Cargo.toml 的wgpu-core与wgpu-types版本统一改成V_WGPU。这一步保证 vendor 代码与本仓库 Cargo workspace 规范兼容。patchReadme:幂等地向ext/webgpu/README.md注入/替换## Source段落,声明该目录的 canonical source 在上游仓库、本副本由该脚本同步。脚本会先删除已有的 Source 段落再插入,因此重复执行不会累积重复内容。- 最后调用
tools/format.js对新拉入的代码统一跑一遍 dprint 格式化,保证 vendor 代码也符合本仓库格式规范。
这种“整树替换 + 确定性补丁 + 复用本仓库格式化”的模式,是维护第三方 vendor 代码的典型做法:上游升级时 diff 可控、补丁位置固定、格式统一。
copyright_checker.js:全仓版权头合规检查
tools/README.md 对版权检查器的描述:
copyright_checker.jsis used to check copyright headers in the codebase. ... it will check all code files in the repository and report any files that are not properly licensed.
运行方式:
deno run --allow-read --allow-run ./tools/copyright_checker.js
源码走读:检查规则与豁免清单
阅读 tools/copyright_checker.js,其核心逻辑是逐文件读取开头一段字节(readFirstPartOfFile 只读前 1KB,避免为检查版权头而加载整个文件),验证是否以期望的版权行开头:
const copyrightYear = 2026;
const COPYRIGHT_LINE =
`Copyright 2018-${copyrightYear} the Deno authors. MIT license.`;
几个值得展开的规则细节:
- 文件类型与豁免清单:通过
getSources收集*.js、*.mjs、*.jsx、*.ts、*.tsx、*.rs、*.c以及所有Cargo.toml,并用:!:排除模式跳过第三方 vendor 与测试数据,例如cli/tsc/*typescript.js(TypeScript 编译器本体)、cli/tsc/dts/**(内置类型声明)、ext/node/polyfills/deps/**、tests/registry/**、tests/wpt/suite/**等。这份清单本身就是“哪些代码属于 Deno 自研、哪些是引入的上游”的一份隐性边界描述。 - 允许的版权行前缀:
ACCEPTABLE_LINES允许版权行之前出现// deno-lint-*指令、// Copyright*续行、// Ported*(移植代码)、空行或 shebang 行;超过这些容忍范围仍不以版权行开头则报错,并附上“出错的行是……”的诊断。 - Cargo.toml 单独处理:期望以
# Copyright 2018-2026 the Deno authors. MIT license.开头(TOML 注释风格),而 JS/TS/Rust/C 文件期望// Copyright ...(C 风格注释)。 - LICENSE.md 年份联动:脚本最后还检查仓库根 LICENSE.md 是否包含
Copyright 2018-2026 the Deno authors,年份过期会单独报LICENSE.md has old copyright year。 - 错误合并输出:所有错误先收集、最后一次性
console.error(errors.join("\n"))输出,注释解释原因是避免与并行运行的其他工具脚本的输出交错。
该脚本还被 tools/lint.js 以模块方式复用(import { checkCopyright } from "./copyright_checker.js"),所以跑 tools/lint.js 时会顺带完成版权检查。
小结:从 tools/README.md 到完整提交前检查清单
回到 tools/README.md 的主线,可以把整个工具链浓缩成一份可操作的提交前清单:
| 步骤 | 命令 | 底层工具 | 检查/修改范围 |
|---|---|---|---|
| 格式化 | deno run --allow-read --allow-write --allow-run ./tools/format.js |
dprint 0.47.2(配置 .dprint.json),支持 --check 只检测 |
全仓文本文件 |
| 检查(全量) | deno run --allow-read --allow-write --allow-run ./tools/lint.js |
deno lint + clippy + 多个内置约定检查 + 版权检查 | JS/TS 与 Rust 双栈 |
| 检查(仅 JS) | ... ./tools/lint.js --js |
deno lint + 工作流同步、顶层条目、.out 快照等检查 | JS/TS 侧 |
| 用本地构建跑脚本 | cargo run -- run --allow-read --allow-write --allow-run ./tools/<script> |
cargo 构建中的 deno 二进制 | — |
| WebGPU 上游同步 | 修改 COMMIT/V_WGPU 后运行 ./tools/wgpu_sync.js |
curl + tar + 正则补丁 + format.js | ext/webgpu/ |
| 版权检查 | deno run --allow-read --allow-run ./tools/copyright_checker.js |
自研脚本 | 全仓源码头 + LICENSE.md |
从源码结构看,这套工具链的设计哲学很清晰:所有格式与静态检查规则收敛在单一入口脚本与单一配置文件中(dprint 配置只有一个 .dprint.json,clippy 规则集中在 tools/lint.js 的 deny 清单与各 crate 的 clippy.toml),上游 vendor 用可重放的脚本管理(tools/wgpu_sync.js),仓库结构本身也被当作被检查对象(顶层条目白名单、工作流文件生成同步)。对希望参与 Deno 开发或研究其工程实践的读者,直接阅读 tools/ 目录下的这些脚本,是理解 Deno 如何大规模维护一个 Rust + 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 StartedRust0625
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