lint-staged/lint-staged
GitHub: lint-staged/lint-staged
针对 Git 暂存文件运行格式化和代码检查任务的工具,确保只有符合规范的代码才能进入仓库。
Stars: 14618 | Forks: 456
# 🚫💩 lint-staged
[](https://badge.fury.io/js/lint-staged)
针对暂存的 git 文件运行格式化和 lint 等任务,不要让 :poop: 溜进你的代码库!
```
npm install --save-dev lint-staged # requires further setup
```
```
$ git commit
✔ Backed up original state in git stash (5bda95f)
❯ Running tasks for staged files...
❯ packages/frontend/.lintstagedrc.json — 1 file
↓ *.js — no files [SKIPPED]
❯ *.{json,md} — 1 file
⠹ prettier --write
↓ packages/backend/.lintstagedrc.json — 2 files
❯ *.js — 2 files
⠼ eslint --fix
↓ *.{json,md} — no files [SKIPPED]
◼ Updating Git index again...
◼ Cleaning up temporary files...
```
## 目录
- [为什么](#why)
- [安装和设置](#installation-and-setup)
- [更新日志](#changelog)
- [命令行标志](#command-line-flags)
- [配置](#configuration)
- [过滤文件](#filtering-files)
- [支持哪些命令?](#what-commands-are-supported)
- [按顺序运行多个命令](#running-multiple-commands-in-a-sequence)
- [使用 JS 配置文件](#using-js-configuration-files)
- [重新格式化代码](#reformatting-the-code)
- [示例](#examples)
- [常见问题](#frequently-asked-questions)
## 为什么
像格式化和 lint 这样的代码质量检查任务,在提交代码之前运行会更有意义。这样做可以确保没有错误进入代码库,并强制执行代码风格。但是在整个项目上运行一个任务可能会很慢,而且像 lint 这种带有主观性的任务有时会产生不相关的结果。最终,你只想检查那些将要被提交的文件。
这个项目包含一个脚本,它将运行任意的 shell 任务,并以指定的 glob 模式过滤出的暂存文件列表作为参数。
### 相关博客文章和演讲
- [介绍性 Medium 文章 - Andrey Okonetchnikov, 2016](https://medium.com/@okonetchnikov/make-linting-great-again-f3890e1ad6b8#.8qepn2b5l)
- [在每次 Git 提交前运行 Jest 测试 - Ben McCormick, 2017](https://benmccormick.org/2017/02/26/running-jest-tests-before-each-git-commit/)
- [AgentConf 演讲 - Andrey Okonetchnikov, 2018](https://www.youtube.com/watch?v=-mhY7e-EsC4)
- [SurviveJS 采访 - Juho Vepsäläinen 和 Andrey Okonetchnikov, 2018](https://survivejs.com/blog/lint-staged-interview/)
- [使用 `dotnet-format` 和 `lint-staged` 美化你的 CSharp 代码](https://johnnyreilly.com/2020/12/22/prettier-your-csharp-with-dotnet-format-and-lint-staged)
## 安装和设置
要以推荐的方式安装 _lint-staged_,你需要:
1. 安装 _lint-staged_ 本身:
- `npm install --save-dev lint-staged`
2. 设置 `pre-commit` git 钩子来运行 _lint-staged_
- [Husky](https://github.com/typicode/husky) 是配置 git 钩子的热门选择
- 在[这里](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)阅读更多关于 git 钩子的信息
3. 安装一些工具,比如 [ESLint](https://eslint.org) 或 [Prettier](https://prettier.io)
4. 配置 _lint-staged_ 来运行代码检查器和其他任务:
- 例如:`{ "*.js": "eslint" }` 对所有暂存的 JS 文件运行 ESLint
- 查看[配置](#configuration)获取更多信息
不要忘记将 `package.json` 和 `.husky` 的更改提交到代码库,以便与你的团队共享此设置!
现在修改几个文件,使用 `git add` 或 `git add --patch` 将其中一些添加到你的提交中,然后尝试对它们执行 `git commit`。
查看[示例](#examples)和[配置](#configuration)获取更多信息。
## 更新日志
查看[版本发布](https://github.com/lint-staged/lint-staged/releases)。
### 迁移
有关重大更改,请参阅 [MIGRATION.md](./MIGRATION.md)。
## 命令行标志
```
❯ npx lint-staged --help
Usage: lint-staged [options]
-h, --help display this help message
-V, --version display the current version number
--allow-empty allow empty commits when tasks revert all staged changes (default: false)
-p, --concurrent the number of tasks to run concurrently, or false for serial (default: true)
-c, --config [path] path to configuration file, or - to read from stdin
--continue-on-error run all tasks to completion even if one fails (default: false)
--cwd [path] run all tasks in specific directory, instead of the current
-d, --debug print additional debug information (default: false)
--diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".
--diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
--fail-on-changes fail with exit code 1 when tasks modify tracked files (default: false)
--no-hide-partially-staged hide unstaged changes from partially staged files (default: true)
--hide-unstaged hide all unstaged changes, instead of just partially staged (default: false)
--hide-all hide all unstaged changes and untracked files (default: false)
--max-arg-length [number] maximum length of the command-line argument string (default: 0)
-q, --quiet disable lint-staged's own console output (default: false)
-r, --relative pass relative filepaths to tasks (default: false)
--no-revert revert to original state in case of errors (default: true)
--no-stash enable the backup stash (default: true)
-v, --verbose show task output even when tasks succeed; by default only failed output is shown (default: false)
Any lost modifications can be restored from a git stash:
> git stash list --format="%h %s"
On main: lint-staged automatic backup
> git apply --index
```
#### `--allow-empty`
默认情况下,当任务撤销了所有暂存的更改时,lint-staged 将报错退出并中止提交。使用此标志允许创建空的 git 提交。
#### `--concurrent [数字|布尔值]`
控制 lint-staged 运行任务的[并发性](#task-concurrency)。**注意**:这不会影响子任务的并发性(它们将始终按顺序运行)。可能的值有:
- `false`:串行运行所有任务
- `true`(默认):_无限_ 并发。尽可能多地并行运行任务。
- `{number}`:并行运行指定数量的任务,其中 `1` 相当于 `false`。
#### `--config [路径]`
手动指定配置文件的路径或 npm 包名。注意:使用时,lint-staged 不会执行配置文件搜索,如果找不到指定的文件将打印错误。如果提供 `-` 作为文件名,则配置将从 stdin 读取,允许像 `cat my-config.json | npx lint-staged --config -` 这样通过管道传入配置。
#### `--cwd [路径]`
更改 _lint-staged_ 运行任务的工作目录。默认为 `process.cwd()`,该值可以是绝对路径,也可以是相对于默认值的相对路径。
#### `--debug`
在调试模式下运行。设置后,它会执行以下操作:
- 记录有关暂存文件、正在执行的命令、二进制文件位置等的附加信息。
- 对 `listr2` 使用 [`verbose` 渲染器](https://listr2.kilic.dev/renderers/verbose-renderer/);这将导致向终端输出串行的、无颜色的内容,而不是默认的(美化的、动态的)输出。
([`verbose` 渲染器](https://listr2.kilic.dev/renderers/verbose-renderer/)也可以通过设置 `TERM=dumb` 或 `NODE_ENV=test` 环境变量来激活)
#### `--diff`
默认情况下,任务会根据 git 中暂存的所有文件进行过滤,这些文件由 `git diff --staged` 生成。此选项允许你使用任意的修订版本覆盖 `--staged` 标志。例如,要获取两个分支之间已更改文件的列表,请使用 `--diff="branch1...branch2"`。你还可以从 [git diff](https://git-scm.com/docs/git-diff) 和 [gitrevisions](https://git-scm.com/docs/gitrevisions) 阅读更多信息。此选项也隐含了 `--no-stash`。
#### `--diff-filter [字符串]`
默认情况下,仅包含_已添加_、_已复制_、_已修改_或_已重命名_的文件。使用此标志可以用其他值覆盖默认的 `ACMR` 值:_已添加_ (`A`)、_已复制_ (`C`)、_已删除_ (`D`)、_已修改_ (`M`)、_已重命名_ (`R`)、_类型已更改_ (`T`)、_未合并_ (`U`)、_未知_ (`X`) 或_配对已破坏_ (`B`))。另请参阅 `git diff` 文档中的 [--diff-filter](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203)。
#### `--continue-on-error`
默认情况下,当任何配置的任务失败时,_lint-staged_ 将“提前退出”,以确保运行时间短。使用此标志,_lint-staged_ 将改为运行所有任务直到完成,并且只在最后失败,从而允许查看所有任务的输出。
#### `--fail-on-changes`
默认情况下,任务所做的更改会自动暂存并添加到提交中。此标志禁用该行为,并使 _lint-staged_ 以代码 1 退出,从而中止提交。使用此标志也隐含了 `--no-revert` 标志,这意味着任务所做的任何更改在失败后都将保留在工作树中,以便可以手动暂存它们并再次尝试提交。
#### `--max-arg-length [数字]`
当检测到当前 shell 无法处理长命令(大量文件)时,它们会自动分成多个块。使用此标志可以覆盖生成命令字符串的最大长度。
#### `--no-stash`
默认情况下,在运行任务之前会创建一个备份存储,如果发生错误,所有任务修改都将被恢复。此选项将禁用创建存储,并在中止提交时将所有修改保留在索引中。
#### `--no-hide-partially-staged`
默认情况下,部分暂存文件中未暂存的更改将被隐藏,并在运行任务后重新应用。此选项将禁用此行为,导致这些更改也被提交。
#### `--hide-unstaged`
使用此选项可在运行任务前隐藏被跟踪文件中所有未暂存的更改,而不仅仅是那些也被部分暂存的更改。这些更改将在运行任务后重新应用。
#### `--hide-all`
添加新选项 `--hide-all`,用于在运行任务前隐藏所有未暂存的更改和未跟踪的文件。这使得运行像 [Knip](https://knip.dev) 这样检查未使用代码的工具变得更加容易。未跟踪的文件包含在备份存储中,并在运行后自动恢复。
#### `--quiet`
抑制所有 CLI 输出,任务本身的输出除外。
#### `--relative`
将相对于 `process.cwd()`(运行 `lint-staged` 的位置)的文件路径传递给任务。默认为 `false`。
#### `--no-revert`
默认情况下,如果发生错误,所有任务修改都将被恢复。此选项将禁用此行为,并在中止提交之前将任务修改应用到索引中。
#### `--verbose`
即使任务成功也显示任务输出。默认情况下,仅显示失败的输出。
## 配置
_Lint-staged_ 可以通过多种方式进行配置:
- `package.json` 中的 `lint-staged` 对象,或 [`package.yaml`](https://github.com/pnpm/pnpm/pull/1799)
- JSON 或 YML 格式的 `.lintstagedrc` 文件,或者你可以明确指定文件扩展名:
- `.lintstagedrc.json`
- `.lintstagedrc.yaml`
- `.lintstagedrc.yml`
- ESM 格式的 `.lintstagedrc.mjs` 或 `lint-staged.config.mjs` 文件
- 默认导出值应该是一个配置:`export default { ... }`
- CommonJS 格式的 `.lintstagedrc.cjs` 或 `lint-staged.config.cjs` 文件
- 导出值应该是一个配置:`module.exports = { ... }`
- `lint-staged.config.js` 或 `.lintstagedrc.js`,采用 ESM 或 CommonJS 格式,具体取决于
项目的 _package.json_ 是否包含 `"type": "module"` 选项。
- 使用 `--config` 或 `-c` 标志传递配置文件
配置应该是一个对象,其中每个值是要运行的**命令**,其键是用于此命令的 glob 模式。此包使用 [picomatch](https://github.com/micromatch/picomatch) 进行 glob 模式匹配。JavaScript 文件还可以导出高级配置作为函数。有关更多信息,请参见[使用 JS 配置文件](#using-js-configuration-files)。
你还可以在项目内的不同目录中放置多个配置文件。对于给定的暂存文件,将始终使用最近的配置文件,并且任务默认在配置所在的目录中运行(例如,monorepo 中特定包的目录)。有关更多信息和示例,请参见[“如何在多包 monorepo 中使用 `lint-staged`?”](#how-to-use-lint-staged-in-a-multi-package-monorepo)。
#### `package.json` 示例:
```
{
"lint-staged": {
"*": "your-cmd"
}
}
```
#### `.lintstagedrc` 示例
```
{
"*": "your-cmd"
}
```
此配置将执行 `your-cmd`,并将当前暂存文件的列表作为参数传递。
因此,考虑到你执行了 `git add file1.ext file2.ext`,lint-staged 将运行以下命令:
`your-cmd file1.ext file2.ext`
### TypeScript
_Lint-staged_ 提供了配置和主要 Node.js API 的 TypeScript 类型。你可以在 JS 配置文件中使用 JSDoc 语法:
```
/**
* @filename: lint-staged.config.js
* @type {import('lint-staged').Configuration}
*/
export default {
'*': 'prettier --write',
}
```
如果你的 Node.js 版本支持,也可以为配置使用 `.ts` 文件扩展名。`--experimental-strip-types` 标志在 [Node.js v22.6.0](https://github.com/nodejs/node/releases/tag/v22.6.0) 中引入并在 [v23.6.0](https://github.com/nodejs/node/releases/tag/v23.6.0) 中解除实验状态,使 Node.js 无需额外配置即可执行 TypeScript 文件。
```
export NODE_OPTIONS="--experimental-strip-types"
npx lint-staged --config lint-staged.config.ts
```
### 任务并发
默认情况下,_lint-staged_ 将同时运行配置的任务。这意味着对于每个 glob,所有命令都将同时启动。使用以下配置,`eslint` 和 `prettier` 将同时运行:
```
{
"*.ts": "eslint",
"*.md": "prettier --list-different"
}
```
由于 glob 不会重叠,并且命令不会更改文件,而只报告可能的错误(中止 git 提交),因此这通常不会成为问题。如果你想对同一组文件运行多个命令,可以使用数组语法以确保命令按顺序运行。在以下示例中,`prettier` 将对两个 glob 运行,此外 `eslint` 将在它_之后_对 `*.ts` 文件运行。两组命令(针对每个 glob)仍然同时启动(但不重叠)。
```
{
"*.ts": ["prettier --list-different", "eslint"],
"*.md": "prettier --list-different"
}
```
当配置的 glob 重叠且任务对文件进行编辑时,请格外小心。例如,在此配置中,`prettier` 和 `eslint` 可能会尝试同时更改同一个 `*.ts` 文件,从而导致_竞态条件_:
```
{
"*": "prettier --write",
"*.ts": "eslint --fix"
}
```
你可以使用否定模式和数组语法来解决它:
```
{
"!(*.ts)": "prettier --write",
"*.ts": ["eslint --fix", "prettier --write"]
}
```
另一个任务对文件进行编辑且 glob 匹配多个文件但不重叠的示例:
```
{
"*.css": ["stylelint --fix", "prettier --write"],
"*.{js,jsx}": ["eslint --fix", "prettier --write"],
"!(*.css|*.js|*.jsx)": ["prettier --write"]
}
```
或者,如有必要,你可以使用 `--concurrent ` 限制并发,或使用--concurrent false` 完全禁用它。
## 过滤文件
任务命令作用于所有暂存文件的一个子集,该子集由 _glob 模式_ 定义。lint-staged 使用 [picomatch](https://github.com/micromatch/picomatch) 根据以下规则匹配文件:
- 如果 glob 模式不包含斜杠 (`/`),则 picomatch 的 `matchBase` 选项将被启用,因此 glob 无论在哪个目录都会匹配文件的基本名称:
- `"*.js"` 将匹配所有 JS 文件,如 `/test.js` 和 `/foo/bar/test.js`
- `"!(*test).js"` 将匹配所有 JS 文件,除了以 `test.js` 结尾的文件,所以是 `foo.js` 而不是 `foo.test.js`
- `"!(*.css|*.js)"` 将匹配所有文件,除了 CSS 和 JS 文件
- 如果 glob 模式确实包含斜杠 (`/`),它也会匹配路径:
- `"./*.js"` 将匹配 git 仓库根目录中的所有 JS 文件,因此是 `/test.js` 而不是 `/foo/bar/test.js`
- `"foo/**/*.js"` 将匹配 `/foo` 目录内的所有 JS 文件,因此是 `/foo/bar/test.js` 而不是 `/test.js`
匹配时,lint-staged 将执行以下操作
- 自动解析 git 根目录,无需配置。
- 选取项目目录中存在的暂存文件。
- 使用指定的 glob 模式过滤它们。
- 将绝对路径作为参数传递给任务。
**注意:** `lint-staged` 会将_绝对_路径传递给任务,以避免在它们于不同的工作目录执行时发生任何混淆(即当你的 `.git` 目录与 `package.json` 目录不同时)。
另请参见[如何在多包 monorepo 中使用 `lint-staged`?](#how-to-use-lint-staged-in-a-multi-package-monorepo)
### 忽略文件
`lint-staged` 的概念是对 git 中暂存的文件运行配置的 lint 任务(或其他任务)。`lint-staged` 将始终把所有暂存文件的列表传递给任务,并且忽略任何文件的操作应该在任务本身中进行配置。
考虑一个使用 [`prettier`](https://prettier.io/) 在所有文件之间保持代码格式一致的项目。该项目还在 `vendor/` 目录中存储了压缩的第三方供应商库。为了防止 `prettier` 在这些文件上抛出错误,应将 vendor 目录添加到 prettier 的忽略配置(即 `.prettierignore` 文件)中。运行 `npx prettier .` 将忽略整个 vendor 目录,不抛出任何错误。当 `lint-staged` 被添加到项目中并配置为运行 prettier 时,vendor 目录中所有修改和暂存的文件将被 prettier 忽略,即使它接收到了它们作为输入。
在高级场景中,如果无法配置 lint 任务本身来忽略文件,但某些暂存文件仍应被 `lint-staged` 忽略,则可以使用函数语法在将文件路径传递给任务之前对其进行过滤。请参见[示例:从匹配中忽略文件](#example-ignore-files-from-match)。
## 支持哪些命令?
支持通过 `npm` 在本地或全局安装的任何可执行文件,以及来自你的 \$PATH 的任何可执行文件。
`lint-staged` 使用 [tinyexec](https://github.com/tinylibs/tinyexec) 来生成(spawn)本地安装的命令。因此,在你的 `.lintstagedrc` 中你可以这样写:
```
{
"*.js": "eslint --fix"
}
```
这将导致 _lint-staged_ 运行 `eslint --fix file-1.js file-2.js`,此时你已暂存的文件为 `file-1.js`、`file-2.js` 和 `README.md`。
像在 shell 中操作一样,使用空格分隔以将参数传递给你的命令。请参阅下面的[示例](#examples)。
## 按顺序运行多个命令
你可以在每个 glob 上按顺序运行多个命令。为此,请传递一个命令数组而不是单个命令。这对于运行像 `eslint --fix` 或 `stylefmt` 这样的自动格式化工具非常有用,但也可用于任何任意顺序的操作。
例如:
```
{
"*.js": ["eslint", "prettier --write"]
}
```
将会执行 `eslint`,如果它以代码 `0` 退出,它将对所有暂存的 `*.js` 文件执行 `prettier --write`。
这将导致 _lint-staged_ 运行 `eslint file-1.js file-2.js`,当你暂存了文件 `file-1.js`、`file-2.js` 和 `README.md` 时,并且如果它通过了,将继续运行 `prettier --write file-1.js file-2.js`。
## 使用 JS 配置文件
用 JavaScript 编写配置文件是配置 lint-staged 最强大的方式(`lint-staged.config.js`、[类似的文件](https://github.com/lint-staged/lint-staged#configuration),或通过 `--config` 传入)。从配置文件中,你可以导出单个函数或对象。
如果 `exports` 值是一个函数,它将接收包含所有暂存文件名的数组。然后,你可以为这些文件构建自己的匹配器,并返回命令字符串或命令字符串数组。这些字符串被视为完整的字符串,如果需要,应包含文件名参数。
如果 `exports` 值是一个对象,则其键应为 glob 匹配(类似于普通的非 js 配置格式)。其值可以像普通配置中那样,也可以是如上所述的单个函数。导出对象中的函数将不再接收所有匹配的文件,而是只接收与相应 glob 键匹配的暂存文件。
总而言之,默认情况下 _lint-staged_ 会自动将匹配的暂存文件列表添加到你的命令中,但当使用 JS 函数构建命令时,需要你手动完成此操作。例如:
```
export default {
'*.js': (stagedFiles) => [`eslint .`, `prettier --write ${stagedFiles.join(' ')}`],
}
```
这将导致 _lint-staged_ 首先运行 `eslint .`(匹配_所有_文件),如果它通过,当你暂存了文件 `file-1.js`、`file-2.js` 和 `README.md` 时,它将运行 `prettier --write file-1.js file-2.js`。
### JavaScript 函数
你还可以配置 _lint-staged_ 以直接运行 JavaScript/Node.js 脚本,并将暂存文件列表作为参数传递:
```
export default {
'*.js': {
title: 'Log staged JS files to console',
task: async (files) => {
console.log('Staged JS files:', files)
},
},
}
```
### 示例:导出一个函数来构建你自己的匹配器
### 示例:将文件名用单引号括起来并每个文件运行一次
### 示例:在 TypeScript 文件发生更改时运行 `tsc`,但不传递任何文件名参数
### 示例:如果暂存文件超过 10 个,则对整个仓库运行 ESLint
### 示例:使用你自己的 glob
### 示例:从匹配中忽略文件
### 示例:为命令使用相对路径
## 重新格式化代码
像 [Prettier](https://prettier.io)、ESLint/TSLint 或 stylelint 这样的工具可以通过运行 `prettier --write`/`eslint --fix`/`tslint --fix`/`stylelint --fix` 根据相应的配置重新格式化你的代码。只要没有错误,Lint-staged 就会自动将任何修改添加到提交中。
```
{
"*.js": "prettier --write"
}
```
在版本 10 之前,任务必须手动包含 `git add` 作为最后一步。此行为已集成到 lint-staged 本身中,以防止多个任务编辑相同文件时出现竞态条件。如果 lint-staged 检测到任务配置中包含 `git add`,它将在控制台中显示警告。请在升级后从你的配置中删除 `git add`。
## 示例
所有示例均假设你已经在 `package.json` 文件中设置了 lint-staged,并在其单独的配置文件中设置了 [husky](https://github.com/typicode/husky)。
```
{
"name": "My project",
"version": "0.1.0",
"scripts": {
"my-custom-script": "linter --arg1 --arg2"
},
"lint-staged": {}
}
```
在 `.husky/pre-commit` 中
```
# .husky/pre-commit
npx lint-staged
```
_注意:我们不会为运行器传递路径作为参数。这很重要,因为 lint-staged 会为你做这件事。_
### 对 `*.js` 和 `*.jsx` 使用默认参数运行 ESLint,作为 pre-commit 钩子运行
### 使用 `--fix` 自动修复代码风格并添加到提交
### 复用 npm script
### 在任务命令中使用环境变量
### 对 Prettier 支持的任何格式使用 `prettier` 自动修复代码风格
### 对 JavaScript、TypeScript、Markdown、HTML 或 CSS 使用 `prettier` 自动修复代码风格
### 对 CSS 使用默认设置的 Stylelint,对 SCSS 使用 SCSS 语法的 Stylelint
### 运行 PostCSS 排序并用 Stylelint 进行检查
### 压缩图片
更多关于
[imagemin-lint-staged](https://github.com/tomchentw/imagemin-lint-staged) 是专为 lint-staged 使用而设计的命令行工具,具有合理的默认值。
在[这篇博文](https://medium.com/@tomchentw/imagemin-lint-staged-in-place-minify-the-images-before-adding-to-the-git-repo-5acda0b4c57e)中查看关于此方法好处的更多信息。
### 使用 flow 对暂存文件进行类型检查
### 与 Next.js 集成
## 常见问题
### 提交钩子的输出看起来很奇怪(没有颜色,重复行,Windows 上输出冗长,...)
### 我可以通过 node 使用 `lint-staged` 吗?
### 结合 JetBrains IDE 使用 _(WebStorm, PyCharm, IntelliJ IDEA, RubyMine, 等)_
### 如何在多包 monorepo 中使用 `lint-staged`?
### 我可以检查当前项目文件夹之外的文件吗?
### 我可以在 CI 中运行 `lint-staged`,或者在没有暂存文件时运行吗?
### 我可以将 `lint-staged` 与 `ng lint` 一起使用吗
### 如何忽略 `.eslintignore` 中的文件?
#### ESLint >= 7
#### ESLint >= 8.51.0 && [Flat ESLint 配置](https://eslint.org/docs/latest/use/configure/configuration-files-new)
### 如何解决 TypeScript (`tsc`) 在通过 Husky 钩子运行 `lint-staged` 时忽略 `tsconfig.json` 的问题?
查看 asciinema 视频
[](https://asciinema.org/a/199934)点击展开
``` // lint-staged.config.js import picomatch from 'picomatch' export default (allStagedFiles) => { const shFiles = allStagedFiles.filter((file) => picomatch.isMatch(file, ['**/src/**/*.sh'])) if (shFiles.length) { return `printf '%s\n' "Script files aren't allowed in src directory" >&2` } const codeFiles = allStagedFiles.filter((file) => picomatch.isMatch(file, ['**/*.js', '**/*.ts'])) const docFiles = allStagedFiles.filter((file) => picomatch.isMatch(file, ['**/*.md'])) return [`eslint ${codeFiles.join(' ')}`, `mdl ${docFiles.join(' ')}`] } ```点击展开
``` // .lintstagedrc.js export default { '**/*.js?(x)': (filenames) => filenames.map((filename) => `prettier --write '${filename}'`), } ```点击展开
``` // lint-staged.config.js export default { '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit', } ```点击展开
``` // .lintstagedrc.js export default { '**/*.js?(x)': (filenames) => filenames.length > 10 ? 'eslint .' : `eslint ${filenames.join(' ')}`, } ```点击展开
如果你的用例是这样的,最好使用[基于函数的配置(见上文)](https://github.com/lint-staged/lint-staged#example-export-a-function-to-build-your-own-matchers)。 ``` // lint-staged.config.js import picomatch from 'picomatch' export default { '*': (allFiles) => { const codeFiles = allFiles.filter((file) => picomatch.isMatch(file, ['**/*.js', '**/*.ts'])) const docFiles = allFiles.filter((file) => picomatch.isMatch(file, ['**/*.md'])) return [`eslint ${codeFiles.join(' ')}`, `mdl ${docFiles.join(' ')}`] }, } ```点击展开
如果出于某种原因你想从 glob 匹配中忽略文件,你可以将 `picomatch.isMatch()` 与否定模式一起使用: ``` // lint-staged.config.js import picomatch from 'picomatch' export default { '*.js': (files) => { // from `files` filter those _NOT_ matching `*test.js` const match = files.filter((file) => picomatch.isMatch(file, '!*test.js')) return `eslint ${match.join(' ')}` }, } ``` 请注意,在大多数情况下,glob 可以达到相同的效果。对于上面的示例,等效的 glob 是 `!(*test).js`。点击展开
``` import path from 'path' export default { '*.ts': (absolutePaths) => { const cwd = process.cwd() const relativePaths = absolutePaths.map((file) => path.relative(cwd, file)) return `ng lint myProjectName --files ${relativePaths.join(' ')}` }, } ```点击展开
``` { "*.{js,jsx}": "eslint" } ```点击展开
``` { "*.js": "eslint --fix" } ``` 这将运行 `eslint --fix` 并自动将更改添加到提交。点击展开
如果你希望复用在 package.json 中定义的 npm 脚本: ``` { "*.js": "npm run my-custom-script --" } ``` 以下是等效的写法: ``` { "*.js": "linter --arg1 --arg2" } ```点击展开
任务命令_不支持_扩展环境变量的 shell 约定。要自己启用此约定,请使用诸如 [`cross-env`](https://github.com/kentcdodds/cross-env) 之类的工具。 例如,以下是在所有 `.js` 文件上运行 `jest`,并将 `NODE_ENV` 变量设置为 `"test"` 的示例: ``` { "*.js": ["cross-env NODE_ENV=test jest --bail --findRelatedTests"] } ```点击展开
``` { "*": "prettier --ignore-unknown --write" } ```点击展开
``` { "*.{js,jsx,ts,tsx,md,html,css}": "prettier --write" } ```点击展开
``` { "*.css": "stylelint", "*.scss": "stylelint --syntax=scss" } ```点击展开
``` { "*.scss": ["postcss --config path/to/your/config --replace", "stylelint"] } ```点击展开
``` { "*.{png,jpeg,jpg,gif,svg}": "imagemin-lint-staged" } ```更多关于 imagemin-lint-staged 的信息
[imagemin-lint-staged](https://github.com/tomchentw/imagemin-lint-staged) 是专为 lint-staged 使用而设计的命令行工具,具有合理的默认值。
在[这篇博文](https://medium.com/@tomchentw/imagemin-lint-staged-in-place-minify-the-images-before-adding-to-the-git-repo-5acda0b4c57e)中查看关于此方法好处的更多信息。
点击展开
``` { "*.{js,jsx}": "flow focus-check" } ```点击展开
``` // .lintstagedrc.js // See https://nextjs.org/docs/basic-features/eslint#lint-staged for details const path = require('path') const buildEslintCommand = (filenames) => `next lint --fix --file ${filenames.map((f) => path.relative(process.cwd(), f)).join(' --file ')}` module.exports = { '*.{js,jsx,ts,tsx}': [buildEslintCommand], } ```点击展开
Git 2.36.0 引入了一项针对钩子的更改,即它们不再在原始 TTY 中运行。 此问题已在 2.37.0 中修复: https://raw.githubusercontent.com/git/git/master/Documentation/RelNotes/2.37.0.txt 如果更新 Git 没有帮助,你可以尝试在你的 Git 钩子中手动重定向输出;例如: ``` # .husky/pre-commit if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then exec >/dev/tty 2>&1; fi npx lint-staged ``` 来源:https://github.com/typicode/husky/issues/968#issuecomment-1176848345点击展开
是的! ``` import lintStaged from 'lint-staged' try { const success = await lintStaged() console.log(success ? 'Linting was successful!' : 'Linting failed!') } catch (e) { // Failed to load configuration console.error(e) } ``` `lintStaged` 的参数与其对应的 CLI 参数等效: ``` const success = await lintStaged({ allowEmpty: false, concurrent: true, configPath: './path/to/configuration/file', cwd: process.cwd(), debug: false, maxArgLength: null, quiet: false, relative: false, stash: true, verbose: false, }) ``` 你也可以使用 `config` 选项直接传递配置: ``` const success = await lintStaged({ allowEmpty: false, concurrent: true, config: { '*.js': 'eslint --fix' }, cwd: process.cwd(), debug: false, maxArgLength: null, quiet: false, relative: false, stash: true, verbose: false, }) ``` `maxArgLength` 选项配置将任务分块为多个依次运行的部分。这是为了避免在 Windows 平台上出现问题,因为在该平台上命令行参数字符串的最大长度限制为 8192 个字符。当存在许多暂存文件时,Lint-staged 可能会生成一个非常长的参数字符串。此选项在 CLI 中是自动设置的,但在默认情况下不通过 Node.js API 设置。点击展开
_**更新**_:最新版本的 JetBrains IDE 现已支持如期运行钩子。 当使用 IDE 的 GUI 提交带有 `precommit` 钩子的更改时,你可能会在 IDE 和命令行中看到不一致的情况。这是 JetBrains 的一个[已知问题](https://youtrack.jetbrains.com/issue/IDEA-135454),所以如果你希望此问题得到修复,请在 YouTrack 上为它投票。 在该问题在 IDE 中得到解决之前,你可以以下配置来变通解决: husky v1.x ``` { "husky": { "hooks": { "pre-commit": "lint-staged", "post-commit": "git update-index --again" } } } ``` husky v0.x ``` { "scripts": { "precommit": "lint-staged", "postcommit": "git update-index --again" } } ``` _感谢[此评论](https://youtrack.jetbrains.com/issue/IDEA-135454#comment=27-2710654)提供的修复方法!_点击展开
在 monorepo 的根级别安装 _lint-staged_,并在每个包中添加单独的配置文件。_Lint-staged_ 会找到每个配置文件,并将暂存文件匹配到最近的配置。每个配置文件所在的目录将用作这些任务的工作目录,除非使用了 `--cwd` 选项。这几乎等同于在每个配置中并行运行多个 _lint-staged_ 进程,但避免了多个并行的锁定 Git 操作。 例如,在一个具有 `packages/frontend/.lintstagedrc.json` 和 `packages/backend/.lintstagedrc.json` 的 monorepo 中,`packages/frontend/` 中的一个暂存文件将只匹配该配置,而不匹配 `packages/backend/` 中的配置。 **注意**:_lint-staged_ 不会合并配置文件,因此如果距离暂存文件最近的配置文件与之不匹配,该文件将被忽略。例如: ``` // ./.lintstagedrc.json { "*.md": "prettier --write" } ``` ``` // ./packages/frontend/.lintstagedrc.json { "*.js": "eslint --fix" } ``` 当提交 `./packages/frontend/README.md` 时,它**将不会运行** _prettier_,因为 `frontend/` 目录中的配置距离文件更近,并且未包含该文件。你应该将所有 _lint-staged_ 配置文件视为彼此独立且相互分离的。你始终可以使用 JS 文件来“扩展”配置,例如: ``` import baseConfig from '../.lintstagedrc.js' export default { ...baseConfig, '*.js': 'eslint --fix', } ``` **注意**:如果你只想在 monorepo 中的某一个包内运行 _lint-staged_,你可以直接使用 `--cwd` 选项(例如 `lint-staged --cwd packages/frontend`)。 **注意**:可以通过显式配置 `lint-staged --cwd="."`,在 monorepo 根目录下运行所有任务。点击展开
简而言之:可以,但模式应以 `../` 开头。 默认情况下,`lint-staged` 仅对项目文件夹中存在的文件执行任务(即在安装和运行 `lint-staged` 的位置)。 因此,这个问题_仅在_项目文件夹是 git 仓库内的子文件夹时才有意义。 在某些项目设置中,可能需要绕过此限制。请参见 [#425](https://github.com/lint-staged/lint-staged/issues/425)、[#487](https://github.com/lint-staged/lint-staged/issues/487) 了解更多上下文。 `lint-staged` 为此提供了一个逃生舱(`>= v7.3.0`)。对于以 `../` 开头的模式,允许所有暂存文件与该模式进行匹配。 请注意,像 `*.js`、`**/*.js` 这样的模式仍然只会匹配项目文件,而不匹配父目录或同级目录中的任何文件。 示例仓库:[sudo-suhas/lint-staged-django-react-demo](https://github.com/sudo-suhas/lint-staged-django-react-demo)。点击展开
Lint-staged 默认针对 git 中暂存的文件运行,并且应该在 git pre-commit 钩子等期间运行。也可以覆盖此默认行为,并针对特定差异(例如 两个不同分支之间的所有更改文件)运行。如果你想在 CI 中运行 _lint-staged_,也许你可以将其设置为在_拉取请求_/_合并请求_中将分支与目标分支进行比较。 尝试执行 `git diff` 命令,直到你对结果满意为止,例如: ``` git diff --diff-filter=ACMR --name-only main...my-branch ``` 这将打印出 `main` 和 `my-branch` 之间_已添加_、_已更改_、_已修改_和_已重命名_的文件列表。 然后你可以针对相同的文件运行 lint-staged: ``` npx lint-staged --diff="main...my-branch" ``` 请注意,--diff="main..my-branch" 会将在 `main` 分支上发生了更改但 `my-branch` 还未追赶上的文件检测为更改过的文件。 要仅查看当前分支上相对于 `main` 的更改,你可能希望使用: ``` npx lint-staged --diff="$(git merge-base main HEAD)" ```点击展开
你不应该通过 _lint-staged_ 使用 `ng lint`,因为它是为 lint 整个项目而设计的。相反,你可以像运行 lint-staged 一样,将 `ng lint` 添加到你的 git pre-commit 钩子中。 有关更多详细信息和可能的解决方法,请参见问题 [!951](https://github.com/lint-staged/lint-staged/issues/951)。点击展开
ESLint 会抛出 `warning File ignored because of a matching ignore pattern. Use "--no-ignore" to override` 警告,这会中断 lint 过程(如果你使用了推荐的 `--max-warnings=0`)。 #### ESLint < 7点击展开
基于[此问题](https://github.com/eslint/eslint/issues/9977)中的讨论,决定使用[所概述的脚本](https://github.com/eslint/eslint/issues/9977#issuecomment-406420893)是解决此问题的最佳途径。 因此,你可以设置一个 `.lintstagedrc.js` 配置文件来执行此操作: ``` import { CLIEngine } from 'eslint' export default { '*.js': (files) => { const cli = new CLIEngine({}) return 'eslint --max-warnings=0 ' + files.filter((file) => !cli.isPathIgnored(file)).join(' ') }, } ```点击展开
在 ESLint > 7 的版本中,[isPathIgnored](https://eslint.org/docs/developer-guide/nodejs-api#-eslintispathignoredfilepath) 是一个异步函数,现在返回一个 promise。下面的代码可用于恢复上述功能。 从 [10.5.3](https://github.com/lint-staged/lint-staged/releases) 开始,任何由于不良的 ESLint 配置导致的错误都将显示在控制台中。 ``` import { ESLint } from 'eslint' const removeIgnoredFiles = async (files) => { const eslint = new ESLint() const isIgnored = await Promise.all( files.map((file) => { return eslint.isPathIgnored(file) }) ) const filteredFiles = files.filter((_, i) => !isIgnored[i]) return filteredFiles.join(' ') } export default { '**/*.{ts,tsx,js,jsx}': async (files) => { const filesToLint = await removeIgnoredFiles(files) return [`eslint --max-warnings=0 ${filesToLint}`] }, } ```点击展开
ESLint v8.51.0 引入了 [`--no-warn-ignored` CLI 标志](https://eslint.org/docs/latest/use/command-line-interface#--no-warn-ignored)。它抑制了 `warning File ignored because of a matching ignore pattern. Use "--no-ignore" to override` 警告,因此不再需要通过 `eslint.isPathIgnored` 手动忽略文件。 ``` { "*.js": "eslint --max-warnings=0 --no-warn-ignored" } ``` **注意:** `--no-warn-ignored` 标志仅在使用 [Flat ESLint 配置](https://eslint.org/docs/latest/use/configure/configuration-files-new) 时可用。点击展开
当通过 Husky 钩子运行 `lint-staged` 时,TypeScript 可能会忽略 `tsconfig.json`,从而导致类似以下的错误: 有关更多细节,请参见问题 [#825](https://github.com/lint-staged/lint-staged/issues/825)。 #### 根本原因 1. `lint-staged` 自动将匹配的暂存文件作为参数传递给命令。 2. 某些输入文件会导致 TypeScript 忽略 `tsconfig.json`。有关更多细节,请参见此 TypeScript 问题:[Allow tsconfig.json when input files are specified](https://github.com/microsoft/TypeScript/issues/27379)。 #### 解决方法:为 `tsc` 命令使用[函数签名](https://github.com/lint-staged/lint-staged?tab=readme-ov-file#example-run-tsc-on-changes-to-typescript-files-but-do-not-pass-any-filename-arguments) 正如 @antoinerousseau 在 [#825 (评论)](https://github.com/lint-staged/lint-staged/issues/825#issuecomment-620018284) 中所建议的,使用函数可以防止 `lint-staged` 追加文件参数: **之前:** ``` // package.json "lint-staged": { "*.{ts,tsx}":[ "tsc --noEmit", "prettier --write" ] } ``` **之后:** ``` // lint-staged.config.js module.exports = { '*.{ts,tsx}': [() => 'tsc --noEmit', 'prettier --write'], } ```标签:CMS安全, ESLint, Git Hooks, Git钩子, GNU通用公共许可证, JavaScript, lint-staged, MITM代理, Node.js, Prettier, 代码审查, 代码格式化, 代码规范, 前端工具, 开发效率, 数据可视化, 暂存区, 网络可观测性, 网络安全研究, 网络调试, 自动化, 自定义脚本, 错误基检测, 静态代码分析, 预提交