跳到正文
曾亮懿 ZengLiangYi Blog

代码规范工具:核对 Biome、ESLint 与格式化的真实分工

阅读 ChatCrystal 的实际脚本与 Flat Config,用离线 ESLint 诊断验证规则,再区分语法检查、类型检查、格式化和 CI 门禁。

曾亮懿

曾亮懿

阅读约 4 分钟
目录 展开 收起

工具安装在依赖里,不代表所有检查都已启用。本文对照 ChatCrystal 的实际命令、Biome 开关和客户端 Flat Config,再用离线诊断证明规则真的识别目标问题。重点是可验证的覆盖范围,而不是增加工具数量。

先读脚本,再读工具开关

当前 根 package.json的 pnpm lint 先运行 Biome,成功后再运行 client 的 ESLint。下面是当前脚本摘录:

{
  "lint": "pnpm exec biome check . --diagnostic-level=error && pnpm --filter ./client lint",
  "lint:fix": "pnpm exec biome check --fix . && pnpm --filter ./client lint -- --fix"
}

biome.json启用 linter,formatter 与 assist 均为 false。因此不能把当前 biome check –fix 描述成已经提供全仓库格式化。VCS 忽略规则和 ignoreUnknown 也会影响覆盖范围;命令里的点号不意味着每种文件都经过同样检查。

根依赖没有接入 Prettier、husky 或 lint-staged,仓库也没有 .husky 提交钩子。编辑器保存时自动格式化和本地提交检查可以是后续选择,不能把原稿中的教学安装方案写成已经存在的项目功能。

Flat Config 分别决定匹配、解析与规则

client/eslint.config.js使用 ESLint Flat Config。它全局忽略 dist,对 **/*.{ts,tsx} 组合以下规则集:

配置组 当前作用
@eslint/js recommended 常见 JavaScript 问题规则
typescript-eslint recommended TypeScript 语法与推荐规则
react-hooks flat recommended React Hooks 相关规则
react-refresh vite Vite 刷新场景的导出约定

languageOptions 使用 ecmaVersion:latest 和 browser globals;不是原稿中的固定 2020。React 的 set-state-in-effect 规则被关闭,这是一条实际覆盖,不代表所有 Hooks 规则关闭。全局忽略与只匹配某类文件也要区分,见官方配置说明。

这里使用的 typescript-eslint recommended 不等于启用了依赖类型信息的检查。当前配置没有接入 projectService 等类型信息设置;不能据此声称 ESLint 完成了 TypeScript 编译器的全部类型分析,或默认强制枚举命名风格。需要类型信息的规则有单独的配置条件。

用字面诊断检查规则是否生效

下面安装与当前客户端相同的 ESLint 10.10.0。在新临时目录保存 lesson.mjs,用 Node 24+ 运行。它是独立 JavaScript 教学配置,不加载 ChatCrystal 的配置,也没有 React 或 TypeScript 规则。

npm init -y
npm install --ignore-scripts --no-audit --no-fund eslint@10.10.0
import assert from 'node:assert/strict';
import { ESLint } from 'eslint';

const lint = new ESLint({
  overrideConfigFile: true,
  overrideConfig: [
    {
      files: ['**/*.js'],
      languageOptions: { globals: { console: 'readonly' } },
      rules: { 'no-unused-vars': 'error', 'no-undef': 'error' },
    },
  ],
});
const [bad] = await lint.lintText('const unused = 1;', { filePath: 'fixture.js' });
assert.equal(bad.errorCount, 1);
assert.equal(bad.messages[0].ruleId, 'no-unused-vars');
const [good] = await lint.lintText("console.log('ready');", { filePath: 'fixture.js' });
assert.equal(good.errorCount, 0);
const [missing] = await lint.lintText('missing();', { filePath: 'fixture.js' });
assert.equal(missing.messages[0].ruleId, 'no-undef');
console.log('specific diagnostics and repaired input pass');

把 no-unused-vars 从 error 改为 off,第一条错误数断言应失败;恢复后通过。断言还检查具体 ruleId,避免把解析失败误认成目标规则生效。lintText 返回诊断结果,这个 API 调用本身不会自动按错误数设置进程退出码;教学进程通过 assert 决定成败。

真正接入时应检查一个匹配文件、一个被忽略文件和一个被覆盖规则的文件。也可以在 client 目录运行 pnpm exec eslint --print-config src/App.tsx 查看实际合并配置。配置数组顺序、文件匹配和覆盖值,比“已经安装插件”更能说明实际行为。

格式化选择需要单独落地

如果这个项目要加入格式化,先决定由 Biome 还是 Prettier 负责,并明确支持的文件类型、命令与编辑器设置。启用 Biome formatter 或新增 Prettier 都属于后续方案,本文没有修改配置。

选择 Prettier 时,eslint-config-prettier 可以关闭一组可能冲突的格式规则,但它不替你运行 Prettier,也不是关闭所有 ESLint 质量规则的开关。既有 ESLint 规则未必都与格式化冲突,不能只根据有两个工具就宣布它们互斥。

保存时修复与 CI 检查也有不同任务:前者修改可修复项,后者验证提交内容。自动修复不能保证所有错误消失,应查看 diff 并再次运行检查。提交钩子可以被绕过;CI 是否阻止合并还取决于仓库 required checks,不能从一个 workflow 文件推断保护规则已经设置。

检查命令和失败出口

当前 release.yml的 npm 与 Electron 发布 job 都在构建前运行 pnpm lint、pnpm test。它是标签或手动触发的发布流程,不能概括为每个普通 PR 都自动执行同样的门禁。

一条检查的验收应包含:目标错误是否产生目标诊断,修复后是否通过,忽略是否符合预期,命令失败是否真的让下游停止。类型检查和运行验证另见多包 TypeScript 配置,测试策略也不能由 lint 的绿灯代替。

分享:

相关文章

五种 AI 对话解析器:字段保留与损失边界

从 Claude Code、Codex、Cursor、Trae 和 Copilot 的实际解析器建立验收矩阵,检查工具标记、思考文本、顺序与快照覆盖范围。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #TypeScript #Node.js

AI SDK Provider 抽象:能力表与协议边界

核对 ChatCrystal 的七项 Provider 注册、AI SDK 7 模型工厂和结构化输出,用无网络 mock 验证 Responses、Chat Completions、Embedding 与 schema 失败。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #TypeScript

SourceAdapter 内部链路:身份、内容哈希与重新导入

对照 ChatCrystal 的注册表、导入器和远端载荷,验证会话身份、内容哈希、元数据捷径与 parserVersion 的实际边界。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #TypeScript #Node.js

从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录,欢迎交流实现细节。