第 11 章 · 格式化、lint 与提交钩子
一句话:格式化器统一代码长相,lint 找出可能的错误,提交钩子在
git commit时自动把两者跑一遍,不合格的代码进不了仓库。 用它还是用别的:Python 用 Ruff 一个工具管格式化和 lint;JS、TS 用 Prettier 管格式化、ESLint 管 lint;钩子用 pre-commit 统一管理,两种语言写在同一个配置文件里。 核对版本:Prettier 3.9、ESLint 10.12、Ruff 0.16、pre-commit 4.6,2026-10
11.1 安装与版本
Section titled “11.1 安装与版本”| 工具 | 安装 | 查版本 |
|---|---|---|
| EditorConfig | 不用装,多数编辑器内置支持;VS Code 装扩展,见第 2 章 | 无 |
| Prettier | npm i -D prettier |
npx prettier --version |
| ESLint | npm init @eslint/config@latest |
npx eslint --version |
| Ruff | pip install ruff 或 uv tool install ruff |
ruff --version |
| pre-commit | pip install pre-commit |
pre-commit --version |
Prettier 和 ESLint 装在项目的开发依赖里,用 npx 运行,团队和 CI 用的是 package-lock.json 锁定的同一个版本。npm init @eslint/config@latest 会问要检查哪种文件、用哪种模块写法、有没有框架和 TypeScript、代码跑在浏览器还是 Node,然后装好依赖并写出 eslint.config.js(或 .mjs)。Ruff 和 pre-commit 装进项目的虚拟环境,或用 uv tool install 装成全局命令。
11.2 五分钟概念
Section titled “11.2 五分钟概念”- 格式化(Formatter):只管长相,缩进、空格、引号、换行、行宽,改完代码意思不变。Prettier 和
ruff format是格式化器,几乎不留可调的选项,团队不用再争风格。 - lint(Linter,代码检查):找可能的错误和坏写法,比如用了没定义的变量、导入了没用的模块。ESLint 和
ruff check是 lint 工具,一部分问题加--fix能自动修,剩下的要人改。两者和编辑器的关系见第 3 章。 - 提交钩子(Git hook):Git 在某些时刻自动运行的脚本,放在
.git/hooks/里。pre-commit钩子在每次git commit生成提交之前运行,任何一项失败,这次提交就不生成。 - pre-commit 框架:用一个
.pre-commit-config.yaml列出要跑的钩子,自动下载各钩子的运行环境,Python、JS 项目都能用。配置文件提交进仓库,每个人pre-commit install一次,钩子就装进自己的.git/hooks/。 - 约定式提交(Conventional Commits):提交说明用固定前缀开头,
feat:新功能,fix:修错,docs:文档,chore:杂项。工具能据此自动生成更新日志、决定版本号。
下图是装好钩子后一次提交的路径:钩子改了文件或报了错,提交就中止,改好重新 git add 再提交;通过后推送,CI 再用同样的检查跑一遍,兜住没装钩子的人。
11.3 常用命令
Section titled “11.3 常用命令”| 任务 | 命令 | 说明 |
|---|---|---|
| JS、TS | ||
| 格式化全部文件 | npx prettier --write . |
直接改文件,.gitignore 里的不动 |
| 只检查格式 | npx prettier --check . |
不改文件,列出不合格的;CI 里用 |
| lint | npx eslint . |
有 error 时退出码非 0 |
| lint 并自动修 | npx eslint . --fix |
修不了的照样报出来 |
| Python | ||
| 格式化 | ruff format . |
直接改文件 |
| 只检查格式 | ruff format --check . |
不改文件;CI 里用 |
| lint | ruff check . |
带 [*] 的是能自动修的 |
| lint 并自动修 | ruff check --fix . |
如删掉没用的 import;修不了的照样报出来 |
| pre-commit | ||
| 把钩子装进当前仓库 | pre-commit install |
每次新克隆后都要运行一次 |
| 对全部文件跑一遍 | pre-commit run --all-files |
只查已被 Git 跟踪的文件 |
| 把钩子升到最新版本 | pre-commit autoupdate |
改写配置里每个 rev,改完要提交 |
| 跳过钩子提交 | git commit --no-verify |
只在紧急时用,CI 仍会拦下 |
11.4 典型工作流
Section titled “11.4 典型工作流”给已有的 Python 项目加提交前检查
Section titled “给已有的 Python 项目加提交前检查”- 在项目的虚拟环境里装 pre-commit:
pip install pre-commit。 - 在项目根目录写
.pre-commit-config.yaml,内容见 11.5;先把rev升到当前版本:pre-commit autoupdate。 - 装钩子:
pre-commit install。 - 对历史代码跑一遍,第一次要下载钩子环境,等几分钟:
pre-commit run --all-files。 - 钩子改过的文件看一眼,剩下的报错手动改,再跑到全部
Passed:pre-commit run --all-files。 - 把配置和修过的文件一起提交:
git add -A。 - 提交时钩子自动运行:
git commit -m "chore: 加 pre-commit 钩子"。 - 告诉团队成员拉取后各装一次:
pre-commit install。
给 JS 项目加同样的检查
Section titled “给 JS 项目加同样的检查”Prettier 和 ESLint 用项目里装好的版本,钩子写成 repo: local,同样交给 pre-commit 管:
- 装 Prettier:
npm i -D prettier。 - 生成 ESLint 配置,按提示回答:
npm init @eslint/config@latest。 - 装关掉 ESLint 格式规则的配置,在
eslint.config.js里import eslintConfigPrettier from "eslint-config-prettier/flat",放在配置数组最后:npm i -D eslint-config-prettier。 - 先在命令行核对两样都能跑通:
npx prettier --check . && npx eslint .。 - 按 11.1 装好 pre-commit,在
.pre-commit-config.yaml里写下面的local钩子,再装钩子:pre-commit install。 - 对全部文件跑一遍并修好:
pre-commit run --all-files。 - 提交:
git commit -m "chore: 加 Prettier、ESLint 和 pre-commit"。
repos: - repo: local # 用项目里装好的工具(新克隆先 npm ci) hooks: - id: prettier name: prettier entry: npx prettier --write --ignore-unknown # 不认识的文件类型跳过 language: system # 直接调用本机命令 types: [text] # 只交给它文本文件 - id: eslint name: eslint entry: npx eslint --fix language: system files: \.(js|mjs|cjs|jsx|ts|tsx)$ # 只检查脚本文件在 CI 里再跑一遍
Section titled “在 CI 里再跑一遍”钩子装在每个人自己的 .git/hooks/ 里,不随仓库走,没装或用了 --no-verify 的提交照样能推上来,CI 是最后一道:
- Python 项目用第 10 章 10.4 的模板,里面已有:
ruff check . && ruff format --check .。 - 把模板里 Ruff 的版本写死成
.pre-commit-config.yaml里rev去掉v的那个号:pip install ruff==<版本>。 - JS 项目用第 10 章的 Node 模板,里面已有:
npx prettier --check . && npx eslint .。 - 推送后看检查结果:
gh run watch。
11.5 配置要点
Section titled “11.5 配置要点”| 配置项 | 在哪 | 管什么 |
|---|---|---|
.editorconfig |
项目根目录 | 编辑器的缩进、换行符、文件末尾空行 |
.prettierrc |
项目根目录 | Prettier 的少数可调项,如行宽、分号、引号 |
eslint.config.js |
项目根目录 | ESLint 的规则;package.json 没写 "type": "module" 时用 eslint.config.mjs |
[tool.ruff] |
pyproject.toml |
Ruff 的行宽、目标 Python 版本、启用哪些规则 |
.pre-commit-config.yaml |
项目根目录 | 提交时跑哪些钩子、各用哪个版本 |
这五个文件都提交进仓库,团队成员和 CI 读到的是同一套规则。
最小 .editorconfig。注释必须单独占一行,写在行尾会被当成值的一部分:
# 到这一级为止,不再往上层目录找root = true
# 以下对所有文件生效[*]# 文件编码charset = utf-8# 换行符统一用 LFend_of_line = lf# 用空格缩进,每级 2 格indent_style = spaceindent_size = 2# 文件末尾留一个换行,删掉行尾空格insert_final_newline = truetrim_trailing_whitespace = true
# Python 文件每级缩进 4 格[*.py]indent_size = 4最小 .pre-commit-config.yaml(Python 项目):
repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.10 # Ruff 版本,CI 里的 Ruff 要和它一致 hooks: - id: ruff-check # lint args: [--fix] # 能修的自动修 - id: ruff-format # 格式化,要放在 ruff-check 之后 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 # 通用小钩子的版本 hooks: - id: trailing-whitespace # 删行尾空格 - id: end-of-file-fixer # 文件末尾补一个换行 - id: check-yaml # YAML 语法检查pyproject.toml 里 Ruff 的最小配置:
[tool.ruff]line-length = 100 # 行宽target-version = "py312" # 按 Python 3.12 的语法检查
[tool.ruff.lint]select = ["E", "F", "I", "UP", "B"] # 基本错误、import 排序、新语法、常见坑11.6 注意事项
Section titled “11.6 注意事项”- Prettier 排好的代码,ESLint 又报缩进、引号、分号的问题。 原因:ESLint 开着管格式的规则,和 Prettier 冲突。处理:
npm i -D eslint-config-prettier,在eslint.config.js里导入eslint-config-prettier/flat,放在配置数组最后。 ESLint couldn't find an eslint.config.* file.原因:ESLint 9 起默认只读平面配置(Flat Config)eslint.config.*,旧的.eslintrc.*不再读取。处理:重新生成配置:npm init @eslint/config@latest,规则搬过去后删掉.eslintrc.*。- 第一次提交卡住好几分钟,输出
Installing environment for ...。 原因:pre-commit 在下载并安装每个钩子的运行环境。处理:等它装完,之后有缓存,换了rev才重新装;pre-commit run --all-files可以提前装好。 - 提交失败,钩子显示
Failed和files were modified by this hook。 原因:预期行为,钩子把文件修好了,但修改还在工作区,没进这次提交。处理:看一眼改动,git add <文件>后重新git commit。 - 别人推上来的代码没排版,lint 报错一堆。 原因:他没运行
pre-commit install,或用了--no-verify,钩子只在装过的机器上运行。处理:CI 里再跑一遍同样的检查(见 11.4),设成 PR 的必需检查(见第 9 章)。 - 只改了一行,格式化后
git diff显示整个文件都变了。 原因:换行符(CRLF 和 LF)或缩进设置和原文件不一致。处理:先统一.editorconfig和.gitattributes(见第 7 章 7.5),单独提交一次全量格式化,再做正常修改。
11.7 深入阅读
Section titled “11.7 深入阅读”- EditorConfig:.editorconfig 每个属性的含义,以及哪些编辑器内置支持、哪些要装插件。
- Prettier 文档:可调选项、忽略文件的写法,以及和 ESLint 一起用的说明。
- ESLint 文档:平面配置 eslint.config.js 的写法、规则列表和从 .eslintrc 迁移的指南。
- Ruff 文档:格式化器和 lint 的全部规则、pyproject.toml 里的配置项。
- pre-commit:.pre-commit-config.yaml 的全部键、local 钩子的写法和常用钩子列表。
- 约定式提交(中文):feat、fix 等前缀的规范和破坏性变更的写法。