首页
/ Deno 仓库 JS/TS Lint 工作流:`./x lint-js` 命令链与 `tools/lint.js --js` 管道全解析

Deno 仓库 JS/TS Lint 工作流:`./x lint-js` 命令链与 `tools/lint.js --js` 管道全解析

2026-09-03 16:13:10作者:董斯意

本文围绕 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 lintrecommended 标签规则之上额外启用四条规则(无标签 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/testdatatests/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 一类机制)建立之前,直接引用 ArrayPromise 等全局是不可靠的。为此 tools/lint.js#L151-L225 单独再做一轮 lint:

  • 文件范围是 runtime/**/*.js|tsext/**/*.js|tsext/node/polyfills/*.mjs,排除 *.d.tsext/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 全局名单(ArrayPromiseJSONMap 等数十个内建全局),检查 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 调用。做法是“台账 + 精确比对”:

  1. 用内部插件 tools/lint_plugins/no_deno_api_in_polyfills.ts 对 polyfills 跑一轮 deno lint(该插件导出 EXPECTED_VIOLATIONS,即每个文件被允许的 Deno.* 违例数量);
  2. 用正则 --> path:line:col 解析 lint 输出,得到每个文件的实际违例数;
  3. 逐文件比较:实际数大于期望数则报错 “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.tspr.tsnpm_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.claudecliextlibsruntimeteststoolsxCargo.tomlCLAUDE.mdREADME.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.tslint-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 失败,并用“修复—重跑”闭环收敛到干净状态。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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