Deno 全量代码检查体系:`./x lint` 如何统一 Rust 与 JS/TS 的质量门禁
本文以 Deno 仓库中的 lint-all 技能文档为核心,讲解提交 PR 前如何运行完整的代码检查(Rust clippy + JS/TS deno lint),并深入 tools/lint.js 的源码,剖析该命令背后实际执行的十余项检查:clippy 的强制 deny 规则、bootstrap 代码的 primordials 插件、Node polyfills 中 Deno.* 用量的基线管控、workflow 文件新鲜度校验等。读完后你既能按规范跑通 ./x lint,也能理解每一条门禁的判定逻辑与修复方向。
lint-all 技能:定义、触发时机与约束
仓库为 AI 辅助开发流程内置了名为 lint-all 的技能,其定义位于 SKILL.md,全文内容如下(含 frontmatter):
---
name: lint-all
description: Lint all code (Rust + JS/TS). Use before opening a PR when Rust code was changed.
user-invocable: true
allowed-tools: Bash(./x lint)
---
# Lint All Code
Run the full linter (Rust + JS/TS):
```sh
./x lint
If there are lint errors, fix them and re-run until clean.
这份技能文档虽然简短,但传递了三个明确的操作规范:
1. **触发时机**:当改动涉及 Rust 代码、准备开 PR 之前,必须运行全量 lint;
2. **唯一工具**:`allowed-tools: Bash(./x lint)` 限定了该技能只允许执行 `./x lint` 这一条命令,避免误操作仓库中的其他脚本;
3. **收敛循环**:出现 lint 错误时,修复后必须重新运行,直到输出干净(clean)才算完成。
技能文档本身不含实现细节,真正的检查逻辑全部下沉到仓库根目录的 `x` 开发工具链中。
## 命令入口:从 `./x` 到 `tools/lint.js`
仓库根目录的 [x](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/x?utm_source=gitcode_repo_files) 是一个两行包装脚本:
```sh
#!/usr/bin/env -S deno run --allow-all --ext=ts
import "./tools/x.ts";
它用当前的 deno 运行器加载 tools/x.ts。x 是 Deno 贡献者的统一开发者 CLI(文件头注释说明其设计借鉴了 Servo 的 mach 工具),其中与 lint 直接相关的命令有三个:
| 命令 | 底层执行 | 适用场景 |
|---|---|---|
./x lint |
deno run -A tools/lint.js |
同时改动了 JS/TS 与 Rust 代码(完整检查) |
./x lint-js |
deno run -A tools/lint.js --js |
仅改动了 JS/TS,跳过 Rust clippy,速度快 |
./x verify |
依次执行 ./x fmt 与 ./x lint-js |
提交前的最小验证(格式化 + JS/TS lint) |
tools/x.ts 中 lint 命令的帮助文本明确写着:当只修改 JS/TS 文件时使用 ./x lint-js 更快;若同时改动了 Rust 代码,则应使用包含 clippy 的 ./x lint。这与 lint-all 技能文档中“Rust 代码有变更时才用全量 lint”的要求完全一致。
tools/lint.js 通过命令行参数控制检查范围:
let js = Deno.args.includes("--js");
let rs = Deno.args.includes("--rs");
if (!js && !rs) {
js = true;
rs = true;
}
即不带参数(./x lint)时,JS 与 Rust 两侧检查全部启用;所有子任务以 Promise 并发提交,任一任务失败即 Deno.exit(1)。
Rust 侧:clippy 双段执行与强制 deny 规则
--rs 路径下(见 tools/lint.js 的 clippy() 函数)会执行三项检查:clippy()、ensureNoNonPermissionCapitalLetterShortFlags()、ensureDisallowedMethodsEnforced()。
clippy 的 deny 清单
clippy 调用在默认参数之外追加了一组硬性拒绝项:
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 std 的打印宏在管道被提前关闭(如 deno test | head)时会 panic,仓库规范要求用 log crate 输出诊断信息、用 deno_print 的 drop_println!/drop_eprintln! 宏输出到 stdout。由于 clippy 的报错文案不可定制,runClippy 还会监听子进程 stderr,一旦匹配到 clippy::print[-_]std(out|err),就额外打印一条指向替代方案的提示。
两段式 clippy:workspace 与 deno_core 分开跑
--all-features 无法表达互斥的引擎后端,因此 clippy 分两段执行:
-
workspace 全量(排除 deno_core):先运行
cargo metadata --no-deps拿到工作区成员及其全部 feature,拼成pkg/feature形式的显式 feature 列表,但过滤掉quickjs结尾的 feature 与deno_v8/v8_enable_*前缀的 feature(它们相互冲突),然后执行:cargo clippy --all-targets --features <全部workspace feature> --locked --workspace --exclude deno_core -
deno_core 单独检查:由于 deno_core 的 feature 组合独立于 workspace,单独指定 feature 集合:
cargo clippy -p deno_core --all-targets --locked --features default,unsafe_runtime_options,unsafe_use_unprotected_platform,v8注释标明该调用方式与 deno_core 仓库自身的
tools/lint.ts保持一致。
两段调用在非 debug 构建模式下都会追加 --release(由 buildMode() 判断当前构建模式)。
clippy.toml 的 disallowed-methods 强制校验
ensureDisallowedMethodsEnforced() 不依赖 clippy 本身,而是直接解析每个 crate 的 clippy.toml,确保其中声明了完整的 disallowed-methods 清单。其设计意图是:运行时核心库必须走统一的异步/受控文件抽象,禁止直接调用裸的同步 std 函数。
- ext/ 与 runtime/ 共用的 COMMON_METHODS:覆盖
std::path::Path::canonicalize、std::path::Path::exists等全部 Path 同步方法,std::fs::read、std::fs::write、std::fs::read_dir等全部 fs 方法,以及url::Url::to_file_path/from_file_path/from_directory_path三个 URL-文件路径互转方法; - libs/ 额外追加的 LIBS_EXTRA_METHODS:更严格的隔离——
std::env::var、std::env::current_dir、std::time::SystemTime::now、chrono::Utc::now等,禁止库层直接读环境变量和系统时钟; - 检查范围:遍历
ext/下所有含Cargo.toml的目录、libs/下除core_testing(纯测试 crate)之外的所有 crate,以及runtime/(按 ext 标准)和runtime/permissions(按 libs 标准)。
任何 clippy.toml 缺失或漏写某条 disallowed method 都会被列出并导致 lint 失败。你可以在任意扩展 crate 中查看实际生效的配置,例如 ext/web/clippy.toml 或 ext/fs/clippy.toml。
大写短旗标必须是权限旗标
ensureNoNonPermissionCapitalLetterShortFlags() 用正则扫描 libs/cli_parser/src/defs.rs 中所有 .short('X') 声明,断言去重排序后恰好等于白名单:
A (--allow-all) D (deno install 的 --dev) E (--allow-env)
I (--allow-import) L (日志级别, legacy) N (--allow-net)
O (--save-optional) P (--permission-set) R (--allow-read)
S (--allow-sys) V (version, legacy) W (--allow-write)
其设计动机写在源码注释里:大写短旗标被约定为只表示权限,用户看到 -E 即可知道这是权限开关,便于审查命令行;源码中特别警告“非权限类短旗标不得加入该列表,除非经过讨论”。这是一条把 CLI 设计决策固化为自动检查的典型做法。
JS/TS 侧:deno lint 主流程与排除策略
--js 路径下(lintJS() 函数)生成的临时 lint 配置为:
{
"lint": {
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo", "camelcase", "no-console", "guard-for-in"],
"exclude": ["no-invalid-triple-slash-reference"]
}
}
}
即在 recommended 标签基础上额外启用 ban-untagged-todo(要求 TODO 带作者标签)、camelcase、no-console、guard-for-in 四条规则,并豁免 no-invalid-triple-slash-reference。
扫描范围是仓库中全部 *.js/*.ts,但用 glob 排除项剔除了不适合按本仓库风格检查的内容:ext/** 与 runtime/**(它们由下一节的 bootstrap 专项检查负责)、cli/tsc/dts/** 与 cli/tsc/*typescript.js(TypeScript 编译器自带类型定义)、tests/specs/**、tests/registry/**、各类 benchmark 测试数据等约 25 条排除规则。
一个工程细节是 splitToChunks():由于文件清单可能很长,函数按 MAX_COMMAND_LEN = 30000 字符切分文件列表为若干批次,每批以 deno lint --config=<临时配置> <文件...> 并发子进程执行(stderr: "piped" 以避免与 clippy 的输出交错)。任一批次失败即抛出 deno lint failed。
Bootstrap 代码的 primordials 专项插件
Deno 的运行时 bootstrap 代码(runtime/、ext/ 下的 JS/TS)是污染最敏感的区域:全局对象上的任何属性都可能被用户代码覆盖,导致运行时行为漂移甚至安全漏洞。为此 lintBootstraps() 对 runtime/**、ext/** 及 ext/node/polyfills/*.mjs 单独再跑一遍 deno lint,并在临时配置中挂载内部插件 tools/lint_plugins/prefer_primordials.ts。
该插件实现的 prefer-primordials 规则(注意源码注释:它是内部 Deno lint 插件,而非公共 deno_lint 规则)通过 Deno.lint.Plugin 接口对 AST 做以下检查,每条都带有固定的 message 与 hint:
| 检查项 | 判定 | 修复提示 |
|---|---|---|
| 全局内建引用 | 出现 Array、JSON、Promise、Symbol 等约 60 个 GLOBAL_TARGETS 中的标识符(含 obj.map(...)、[...].slice() 形式的原型链方法/取值器访问) |
改用 primordials 对象中的等价物 |
| 不安全构造/调用 | new Map()、new Set()、new RegExp() 等 UNSAFE_CONSTRUCTOR_TARGETS,以及 Promise.all、Promise.any 等 UNSAFE_FUNCTION_TARGETS |
改用 primordials 的安全包装 |
| null 原型缺失 | ObjectDefineProperty/ReflectDefineProperty 第三参数、ObjectCreate 第二参数的对象字面量未带 __proto__: null;函数默认参数 = {} |
加 __proto__: null |
| 迭代器协议 | 展开运算符、for...of、yield*、数组解构直接消费可被用户污染的对象 |
用 SafeIterator 包裹或改用对象模式 |
| RegExp 字面量 | 直接写 /re/(仅允许 new SafeCtor(/re/) 直传) |
用 SafeRegExp 包裹 |
instanceof / in |
直接比较会走可污染的原型链 | 用 ObjectPrototypeIsPrototypeOf、ObjectHasOwn 或 ReflectHas |
插件的实现细节值得注意:它自己构建了一棵作用域树(buildScopeTreeFixed),区分 var/let/const 的绑定归属,从而准确识别被遮蔽的全局(如 const Array = ... 之后引用 Array 就不算违规);同时通过 insideVarDeclLhsOrMemberExprOrPropOrTypeRef 排除对象键、类方法名、TS 类型引用等非自由变量引用,压低误报。这套逻辑解释了为什么 ext/ 与 runtime/ 的 JS 代码风格与仓库其他部分截然不同——它们是被这条插件持续约束的。
Node polyfills 的 Deno.* 用量基线管控
lintNodePolyfillDenoApis() 挂载第二个内部插件 tools/lint_plugins/no_deno_api_in_polyfills.ts,目标是把 ext/node/polyfills/** 中对 Deno.* 命名空间的依赖逐步迁移到内部 ops 或 ext: 导入。
该插件的规则很简单:任何 Deno 标识符上的属性访问(Deno.readTextFile 之类)都报告一条 no-deno-api 违规。真正巧妙的是 tools/lint.js 中对违规数量的基线比对:
- 插件文件导出
EXPECTED_VIOLATIONS,按文件记录当前允许的最大违规数,例如"ext/node/polyfills/fs.ts": 53、"ext/node/polyfills/process.ts": 31,全库约 26 个文件; - lint 运行时用正则
--> (.+):(\d+):(\d+)解析 deno lint 输出,统计每个文件的实际违规数; - 实际数 > 期望数:报错
New Deno.* API usage is not allowed in node polyfills(新增使用被禁止); - 实际数 < 期望数:同样报错,要求开发者同步下调
EXPECTED_VIOLATIONS中的数值。
这是一个典型的“只允许变好”的反向棘轮(ratchet):迁移一个文件的 Deno.* 用法后必须更新基线,基线数字只能降不能升。
结构性检查:workflow 新鲜度、孤文件与根目录白名单
./x lint 还承担了三类“仓库卫生”检查(均在 --js 路径,且部分依赖 --rs 同时启用):
ensureWorkflowYmlsUpToDate():重新执行 11 个.github/workflows/*.ts生成器(ci.ts、pr.ts、cargo_publish.ts等)的--lint模式,任何一个生成的.generated.yml与现有内容不一致都会失败,并提示Run: <生成器脚本>;ensureNoUnusedOutFiles():遍历 tests/specs 下所有__test__.jsonc测试文件,收集其中output字段(含${variant}占位符展开)引用的.out文件,任何未被引用的.out文件都会导致失败,防止规格测试留下孤儿快照;ensureNoNewTopLevelEntries():用git ls-files枚举仓库根目录条目,与硬编码白名单比对。白名单为:.cargo、.claude、.devcontainer、.github、x、cli、doc、ext、libs、runtime、tests、tools及.dprint.json、Cargo.toml、flake.nix、rust-toolchain.toml等根级文件。注释明确写着“向列表添加任何条目必须经过讨论”,即根目录保持整洁是一条被 lint 强制的仓库规范——lint-all 技能所在的.claude目录本身就在白名单内。
另外,当 --rs 与 --js 同时启用时还会执行 checkCopyright()(由 tools/copyright_checker.js 提供),核对文件头版权行。
实操工作流:从技能到 PR
综合技能文档与源码,提交 PR 前的完整检查流程为:
# 1. 仅 JS/TS 变更时的最小闭环(技能 verify 命令对应的组合)
./x fmt # 格式化(dprint,覆盖 JS/TS/JSON/MD/TOML/Rust)
./x lint-js # 只跑 JS/TS 侧全部检查
# 2. Rust 代码有变更时(lint-all 技能规定的场景)
./x lint # JS/TS + clippy 全量
当 ./x lint 报错时,按错误类型定位修复:
clippy failed/clippy failed for deno_core:按 clippy 输出修复,注意 deny 清单中large_futures、allow_attributes_without_reason(#[allow(...)]必须带理由注释)等非默认规则;deno lint failed/deno lint bootstraps failed:前者修普通 JS/TS 违规,后者按prefer-primordials的 hint 改用primordials对象;mismatched Deno.* API violation counts:按提示在 no_deno_api_in_polyfills.ts 中同步EXPECTED_VIOLATIONS(仅允许减少);<yml> is out of date:运行报错中给出的 workflow 生成器脚本重新生成。
修复后重新执行 ./x lint 直至全部通过——这正是 SKILL.md 中 “fix them and re-run until clean” 的收敛要求。
相关路径索引
| 路径 | 内容 |
|---|---|
| .claude/skills/lint-all/SKILL.md | lint-all 技能定义(本文主体文档) |
| x / tools/x.ts | 开发者 CLI,lint / lint-js / verify 命令注册 |
| tools/lint.js | 全量 lint 编排:并发调度、clippy 参数、规则与排除清单 |
| tools/lint_plugins/prefer_primordials.ts | bootstrap 代码 primordials 内部插件 |
| tools/lint_plugins/no_deno_api_in_polyfills.ts | polyfills Deno.* 用量基线与检测插件 |
| libs/cli_parser/src/defs.rs | 大写短旗标白名单的数据来源 |
| ext/web/clippy.toml | 单个 crate 的 disallowed-methods 配置示例 |
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