Deno 仓库 JS/TS Lint 工作流:`./x lint-js` 命令链与 `tools/lint.js --js` 管道全解析
本文围绕 Deno 仓库中面向 AI 辅助开发的 lint-js 技能(skill)定义展开,讲清 ./x lint-js 这一条命令从入口脚本到底层 tools/lint.js 的完整调用链:--js 模式到底跳过了什么、执行了哪六项检查、规则集与文件范围如何确定、bootstrap 代码为什么需要单独的 primordials 插件。读完后可在 Deno 仓库中独立完成“仅改动 JS/TS 时的提 PR 前校验”,并能看懂并定位 lint 失败输出的具体来源。
一、技能定义:lint-js 是做什么的
.claude/skills/lint-js/SKILL.md 是仓库内一个可被用户显式调用的 Claude Code 技能文件,其完整定义如下:
---
name: lint-js
description: Lint JS/TS code only. Use before opening a PR when only JavaScript
or TypeScript files were changed (no Rust).
user-invocable: true
allowed-tools: Bash(./x lint-js)
---
# Lint JS/TS Code
Run the JS/TS linter:
```sh
./x lint-js
If there are lint errors, fix them and re-run until clean.
从这份简短定义中可以提炼出三个关键约定:
1. **适用场景**:在打开 PR 之前,且本次改动**只涉及 JavaScript/TypeScript 文件、没有 Rust 代码**时使用。若改了 Rust,应改用姊妹技能 [lint-all](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/.claude/skills/lint-all/SKILL.md?utm_source=gitcode_repo_files),其执行 `./x lint`(含 clippy)。
2. **唯一操作命令**:`./x lint-js`。
3. **闭环要求**:出现 lint 错误就修复并重跑,直到输出干净为止。
`user-invocable: true` 与 `allowed-tools: Bash(./x lint-js)` 是技能声明约定:前者表示该技能可被用户主动触发,后者把技能执行期间允许使用的工具限定为运行 `./x lint-js` 这一条 Bash 命令,避免执行过程越权。
这条命令背后的完整实现分散在 [入口脚本](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/x?utm_source=gitcode_repo_files)、[开发者 CLI 实现](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/tools/x.ts?utm_source=gitcode_repo_files) 与 [lint 主脚本](https://gitcode.com/GitHub_Trending/de/deno/blob/e5575e2c0ac3c6ea5281d685bab4c33be8c8b38d/tools/lint.js?utm_source=gitcode_repo_files) 三个文件中,下面沿调用链逐层拆解。
## 二、`./x` 入口:从 shell 脚本到 TypeScript CLI
仓库根的 [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";
也就是说,运行 ./x <command> 实际等价于用本机 PATH 中的 deno 以 --allow-all 权限执行 tools/x.ts。后者自称“x - Developer CLI for contributing to Deno”,灵感来自 Servo 的 mach 工具,为构建、测试、lint 等常见开发任务提供统一入口。
在 tools/x.ts 中,命令通过 buildCommands() 注册成一个 Record<string, Command>,其中 lint-js 的定义位于 tools/x.ts#L87-L100:
const lintJsCmd: Command = {
description: "Lint JavaScript/TypeScript only (skip Rust/clippy)",
help: `Runs linting only on JavaScript and TypeScript files, skipping Rust
clippy. This is significantly faster than './x lint' when you have
not modified any Rust code.
Under the hood:
deno run -A tools/lint.js --js`,
async fn(_args: string[]) {
$.logStep("Linting JavaScript/TypeScript...");
await $`deno run -A tools/lint.js --js`.cwd(root);
$.logStep("JS lint complete.");
},
};
因此 ./x lint-js 的本质就是一条命令:
deno run -A tools/lint.js --js
与之对照,./x lint(对应技能 lint-all)执行的是不带任何标志的 deno run -A tools/lint.js。--js 参数正是两种模式的分水岭。此外,./x <command> --help 会打印 help 字段中的详细说明,./x --help 则列出全部命令(setup、build、check、fmt、lint、lint-js、verify、test-unit 等),这对人和 LLM/Agent 阅读命令语义都很有帮助。
三、--js 标志:选择执行哪些检查任务
tools/lint.js 文件头部通过 shebang 以 deno run --allow-all --config=tests/config/deno.json 运行,随后解析命令行标志并组装并行任务列表(tools/lint.js#L22-L48):
let js = Deno.args.includes("--js");
let rs = Deno.args.includes("--rs");
if (!js && !rs) {
js = true;
rs = true;
}
if (rs) {
promises.push(clippy());
promises.push(ensureNoNonPermissionCapitalLetterShortFlags());
promises.push(ensureDisallowedMethodsEnforced());
}
if (js) {
promises.push(lintJS());
promises.push(lintBootstraps());
promises.push(lintNodePolyfillDenoApis());
promises.push(ensureWorkflowYmlsUpToDate());
promises.push(ensureNoUnusedOutFiles());
promises.push(ensureNoNewTopLevelEntries());
if (rs) {
promises.push(checkCopyright());
}
}
const results = await Promise.allSettled(promises);
for (const result of results) {
if (result.status === "rejected") {
console.error(result.reason);
Deno.exit(1);
}
}
./x lint-js 传入 --js,因此 rs 为 false,最终执行的任务集合是:
| 任务函数 | 检查内容 | 是否属于 --js 路径 |
|---|---|---|
lintJS() |
用 deno lint 按仓库规则集检查 JS/TS 源码 |
是 |
lintBootstraps() |
对 runtime/**、ext/** 的 bootstrap 代码启用 prefer-primordials 插件单独 lint |
是 |
lintNodePolyfillDenoApis() |
校验 node polyfills 中 Deno.* API 用量不超台账 |
是 |
ensureWorkflowYmlsUpToDate() |
校验 .generated.yml 与工作流生成脚本一致 |
是 |
ensureNoUnusedOutFiles() |
校验 tests/specs 中没有无引用的 .out 文件 |
是 |
ensureNoNewTopLevelEntries() |
禁止在仓库根新增未登记的顶层条目 | 是 |
clippy() |
Rust 全 workspace clippy(含额外 deny 规则) | 否(仅 --rs/无参数时) |
ensureNoNonPermissionCapitalLetterShortFlags() |
校验大写短标志只用于权限 | 否 |
ensureDisallowedMethodsEnforced() |
校验各 crate clippy.toml 的 disallowed methods |
否 |
checkCopyright() |
版权头检查 | 否(需 rs && js 同时为真) |
所有任务用 Promise.allSettled 并发执行,任何一个被 reject 都会以退出码 1 结束——这正是 SKILL.md 中“修到 clean 为止”的判定依据:命令非零退出即代表存在 lint 错误。这也解释了技能描述里“跳过 Rust/clippy 因此更快”的说法:--js 模式不触发耗时的 clippy 编译检查。
四、lintJS():规则集、文件范围与分块并行
lintJS()(tools/lint.js#L56-L146)是 JS/TS lint 的主体,分三步:
1. 生成临时 lint 配置。 它先创建一个临时 JSON 文件写入规则集:
JSON.stringify({
lint: {
rules: {
tags: ["recommended"],
include: [
"ban-untagged-todo",
"camelcase",
"no-console",
"guard-for-in",
],
exclude: [
"no-invalid-triple-slash-reference",
],
},
},
})
即在 deno lint 的 recommended 标签规则之上额外启用四条规则(无标签 TODO 禁令、驼峰命名、禁 console、for-in 保护),并显式豁免 no-invalid-triple-slash-reference。临时配置用完后在 finally 中删除。
2. 收集源文件。 文件清单来自 getSources(ROOT_PATH, patterns),实现在 tools/util.js#L203。它以 git pathspec 形式声明包含与排除项(排除语法为 :!:pattern):
const sourceFiles = await getSources(ROOT_PATH, [
"*.js",
"*.ts",
":!:.github/mtime_cache/action.js",
":!:cli/bench/testdata/npm/**",
":!:cli/tsc/dts/**",
":!:cli/tsc/*typescript.js",
":!:cli/tsc/compiler.d.ts",
":!:ext/**",
":!:runtime/**",
":!:libs/**",
":!:tests/bench/testdata/npm/*",
":!:tests/registry/**",
":!:tests/specs/**",
":!:tests/testdata/**",
":!:tests/unit_node/testdata/**",
":!:tests/wpt/runner/**",
":!:tests/wpt/suite/**",
// …(另有若干 testdata 排除项)
]);
从源码结构看,getSources 底层通过 git ls-files(对使用 jj 的检出则转换为等价 fileset 查询)获取受版本控制的文件,因此临时未跟踪文件不会进入 lint 范围。被排除的目录有两类:一类是测试夹具/外部依赖(tests/testdata、tests/registry、bench testdata、WPT 套件等,这些是断言输入而非项目代码);另一类是运行时 bootstrap 代码(ext/**、runtime/**、libs/**),它们不在此处检查,而是交由下一节介绍的 bootstrap 专用 lint 运行处理。
3. 分块并行执行 deno lint。 由于仓库 JS/TS 文件量大,单条命令可能超出系统 argv 长度限制,splitToChunks()(tools/lint.js#L377-L391)以 30000 字符为上限把文件列表切成多个块,然后对每一块并行发起:
const cmd = new Deno.Command(Deno.execPath(), {
cwd: ROOT_PATH,
args: ["lint", "--config=" + configPath, ...chunk],
stderr: "piped",
});
注意这里用 Deno.execPath() 调起当前正在运行的同一个 deno 二进制执行 lint 子命令,保证 lint 器版本与被测仓库一致。任一子进程退出码大于 0 时,会打印 ------ deno lint ------ 分隔线并透出该块的 stderr,随后整体任务抛错。
五、lintBootstraps():bootstrap 代码与 prefer-primordials 插件
Deno 运行时的 bootstrap JS(runtime/** 与 ext/** 下的初始脚本)对全局对象有严格约束:在 primordials(00_primordials.js 一类机制)建立之前,直接引用 Array、Promise 等全局是不可靠的。为此 tools/lint.js#L151-L225 单独再做一轮 lint:
- 文件范围是
runtime/**/*.js|ts、ext/**/*.js|ts与ext/node/polyfills/*.mjs,排除*.d.ts、ext/node/polyfills/deps/**与runtime/cpu_profiler/flamegraph.js; - 临时配置在常规规则之外挂上了内部 lint 插件:
tools/lint_plugins/prefer_primordials.ts,其文件头注释明确写着 “Internal-only: used by tools/lint.js on runtime/ and ext/ bootstrap code”。
该插件(tools/lint_plugins/prefer_primordials.ts)维护了一张 GLOBAL_TARGETS 全局名单(Array、Promise、JSON、Map 等数十个内建全局),检查 bootstrap 代码是否应改为使用对应的 primordials 访问方式;splitToChunks + 并行 deno lint 的执行方式与 lintJS() 相同,失败时打印 ------ deno lint bootstraps ------ 分隔线。
这意味着:改动 ext/、runtime/ 下的 JS 后运行 ./x lint-js,除了常规规则,还会被 primordials 插件审查——这是普通项目里见不到的 Deno 仓库特有约束。
六、lintNodePolyfillDenoApis():用“台账”冻结 node polyfills 的 Deno.* 依赖
tools/lint.js#L230-L375 实现了一个依赖迁移管控机制:node polyfills(ext/node/polyfills/)应逐步迁移到内部 ops 或 ext: 导入,不应新增 Deno.* API 调用。做法是“台账 + 精确比对”:
- 用内部插件 tools/lint_plugins/no_deno_api_in_polyfills.ts 对 polyfills 跑一轮
deno lint(该插件导出EXPECTED_VIOLATIONS,即每个文件被允许的Deno.*违例数量); - 用正则
--> path:line:col解析 lint 输出,得到每个文件的实际违例数; - 逐文件比较:实际数大于期望数则报错 “New Deno.* API usage is not allowed in node polyfills.”;实际数小于期望数(说明有人删掉了违例)则要求同步更新插件里的
EXPECTED_VIOLATIONS台账。
即该检查同时禁止“新增”和“悄悄减少”:迁移进度必须通过更新台账显式体现,任何漂移都是硬错误。
七、--js 路径上的三项仓库结构检查
除了代码风格,./x lint-js 还顺带维护仓库卫生,这三项检查都以纯 TS 逻辑完成:
工作流 YAML 与生成脚本保持一致。 ensureWorkflowYmlsUpToDate()(tools/lint.js#L523-L554)遍历 .github/workflows/ 下的 10 个生成脚本(ci.ts、pr.ts、npm_publish.ts 等),以 deno run --allow-read=. --allow-net=jsr.io <gen> --lint 方式校验对应 .generated.yml 是否过期;过期则报错并提示重新运行生成脚本。
spec 测试无孤儿 .out 文件。 ensureNoUnusedOutFiles() 收集 tests/specs/ 下所有 .out 文件,再解析各 __test__.jsonc(含 variants 变量替换)中声明的 output 引用,凡是没有任何测试引用的 .out 都会导致失败——保证测试期望文件与用例同步增删。
禁止新增顶层条目。 ensureNoNewTopLevelEntries()(tools/lint.js#L812-L858)用 git 已跟踪文件推导仓库根的现有条目,与一份白名单(.cargo、.claude、cli、ext、libs、runtime、tests、tools、x、Cargo.toml、CLAUDE.md、README.md 等)比对,出现白名单外的新顶层目录/文件即报错。源码注释特别强调 “When adding anything to this list it must be discussed! Keep the root of the repository clean.”
八、与仓库 Git 工作流的衔接
CLAUDE.md 的 “Git workflow” 一节把本技能放进了提交流程的标准位置:
- 提交前确保已运行
tools/format.js做格式化(对应./x fmt); - 只改非 Rust 代码时,提交前运行
tools/lint.js --js并修复所有 lint 错误——即本技能执行的./x lint-js的等价命令; - 改了 Rust 代码时则运行不带标志的
tools/lint.js(等价于./x lint,额外执行 clippy 等检查)。
tools/x.ts 中还有一个组合命令 verify(“Pre-commit verification (fmt + lint-js)”),等价于依次执行 ./x fmt 与 ./x lint-js,并提示:若还改了 Rust,应补充运行 ./x lint 与 ./x check。可以推断,lint-js 技能正是把这条最小校验路径封装成了 Agent 可直接触发的一条命令。
九、实操速查
# 仅改动 JS/TS 后的标准校验(本技能推荐路径)
./x lint-js
# 等价的手工调用(便于在无 ./x 环境时排查)
deno run -A tools/lint.js --js
# 改动了 Rust 时改用全量 lint(含 clippy)
./x lint # 等价于 deno run -A tools/lint.js
# 组合校验:格式化 + JS/TS lint
./x verify
# 查看命令详情(help 字段即为面向人/Agent 的文档)
./x lint-js --help
排错时按输出分隔线定位来源:------ deno lint ------ 对应 lintJS(),------ deno lint bootstraps ------ 对应 bootstrap 插件检查,------ node polyfills Deno API usage ------ 对应 Deno.* 台账漂移。常见判定:
- 报
deno lint failed:常规规则(recommended + 4 条 include 规则)违规,按 stderr 中的file:line:col修复后重跑; - 报 “expected N Deno.* violations but found M”:新增/删除了 node polyfill 中的
Deno.*调用,前者应改为内部 ops/ext:导入,后者应更新 tools/lint_plugins/no_deno_api_in_polyfills.ts 中的EXPECTED_VIOLATIONS; - 报 “is out of date”:工作流 YAML 过期,按提示运行对应生成脚本。
修复后重跑 ./x lint-js,直到以退出码 0 结束,即满足 SKILL.md 定义的“re-run until clean”闭环,可以安全地打开 PR。
十、小结
lint-js 技能本身只有一行命令,但其背后是一条层次分明的流水线:x 入口脚本 → tools/x.ts 的 lint-js 命令(deno run -A tools/lint.js --js)→ tools/lint.js 中以 --js 为开关的六项并发检查——覆盖 deno lint 规则集检查、bootstrap 代码的 primordials 插件检查、node polyfills 的 Deno.* 台账管控、工作流 YAML 新鲜度、spec 测试孤儿文件与仓库根目录白名单。理解这条链路后,你既能作为贡献者按仓库约定完成 JS/TS 改动的前置校验,也能作为 Agent 依据命令输出精确归因每一类 lint 失败,并用“修复—重跑”闭环收敛到干净状态。
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 StartedRust0622
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