首页
/ Deno 全量代码检查体系:`./x lint` 如何统一 Rust 与 JS/TS 的质量门禁

Deno 全量代码检查体系:`./x lint` 如何统一 Rust 与 JS/TS 的质量门禁

2026-09-05 18:09:45作者:郁楠烈Hubert

本文以 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.tsx 是 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.tslint 命令的帮助文本明确写着:当只修改 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.jsclippy() 函数)会执行三项检查: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_printdrop_println!/drop_eprintln! 宏输出到 stdout。由于 clippy 的报错文案不可定制,runClippy 还会监听子进程 stderr,一旦匹配到 clippy::print[-_]std(out|err),就额外打印一条指向替代方案的提示。

两段式 clippy:workspace 与 deno_core 分开跑

--all-features 无法表达互斥的引擎后端,因此 clippy 分两段执行:

  1. 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
    
  2. 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::canonicalizestd::path::Path::exists 等全部 Path 同步方法,std::fs::readstd::fs::writestd::fs::read_dir 等全部 fs 方法,以及 url::Url::to_file_path / from_file_path / from_directory_path 三个 URL-文件路径互转方法;
  • libs/ 额外追加的 LIBS_EXTRA_METHODS:更严格的隔离——std::env::varstd::env::current_dirstd::time::SystemTime::nowchrono::Utc::now 等,禁止库层直接读环境变量和系统时钟;
  • 检查范围:遍历 ext/ 下所有含 Cargo.toml 的目录、libs/ 下除 core_testing(纯测试 crate)之外的所有 crate,以及 runtime/(按 ext 标准)和 runtime/permissions(按 libs 标准)。

任何 clippy.toml 缺失或漏写某条 disallowed method 都会被列出并导致 lint 失败。你可以在任意扩展 crate 中查看实际生效的配置,例如 ext/web/clippy.tomlext/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 带作者标签)、camelcaseno-consoleguard-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:

检查项 判定 修复提示
全局内建引用 出现 ArrayJSONPromiseSymbol 等约 60 个 GLOBAL_TARGETS 中的标识符(含 obj.map(...)[...].slice() 形式的原型链方法/取值器访问) 改用 primordials 对象中的等价物
不安全构造/调用 new Map()new Set()new RegExp()UNSAFE_CONSTRUCTOR_TARGETS,以及 Promise.allPromise.anyUNSAFE_FUNCTION_TARGETS 改用 primordials 的安全包装
null 原型缺失 ObjectDefineProperty/ReflectDefineProperty 第三参数、ObjectCreate 第二参数的对象字面量未带 __proto__: null;函数默认参数 = {} __proto__: null
迭代器协议 展开运算符、for...ofyield*、数组解构直接消费可被用户污染的对象 SafeIterator 包裹或改用对象模式
RegExp 字面量 直接写 /re/(仅允许 new SafeCtor(/re/) 直传) SafeRegExp 包裹
instanceof / in 直接比较会走可污染的原型链 ObjectPrototypeIsPrototypeOfObjectHasOwnReflectHas

插件的实现细节值得注意:它自己构建了一棵作用域树(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 同时启用):

  1. ensureWorkflowYmlsUpToDate():重新执行 11 个 .github/workflows/*.ts 生成器(ci.tspr.tscargo_publish.ts 等)的 --lint 模式,任何一个生成的 .generated.yml 与现有内容不一致都会失败,并提示 Run: <生成器脚本>
  2. ensureNoUnusedOutFiles():遍历 tests/specs 下所有 __test__.jsonc 测试文件,收集其中 output 字段(含 ${variant} 占位符展开)引用的 .out 文件,任何未被引用的 .out 文件都会导致失败,防止规格测试留下孤儿快照;
  3. ensureNoNewTopLevelEntries():用 git ls-files 枚举仓库根目录条目,与硬编码白名单比对。白名单为:.cargo.claude.devcontainer.githubxclidocextlibsruntimeteststools.dprint.jsonCargo.tomlflake.nixrust-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_futuresallow_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 配置示例
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384