lint-staged与ls-lint配合使用时的陷阱

问题背景

在最近的一个 React + TypeScript 项目中,我使用了 lint-staged 配合 ls-lint 来实现 Git 提交时的文件名规范自动检查。配置看起来很简单:

json 复制代码
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": ["eslint --fix", "ls-lint"]
  }
}

.ls-lint.yml 配置文件规定了命名规则:

yaml 复制代码
ignore:
  - node_modules
  - .git
  - dist

ls:
  .tsx: PascalCase # .tsx 文件应该使用帕斯卡命名(首字母大写)
  .ts: kebab-case # .ts 文件使用短横线命名
  .js: kebab-case
  .css: kebab-case

按照这个规则,src/main.tsx 这样的文件名(小写开头)应该在提交时被拦截并报错。但奇怪的是,git commit 时检测通过了,而手动执行却报错了。

问题复现

场景一:手动执行 ls-lint(相对路径)

bash 复制代码
$ npx ls-lint src/main.tsx
src/main.tsx failed for `.tsx` rules: pascalcase

✅ 符合预期:检测到命名不规范,退出码为 1。

场景二:Git 提交时通过 lint-staged 执行

bash 复制代码
$ git add src/main.tsx
$ git commit -m "test"
✔ Preparing...
✔ Running tasks...
  ✔ eslint --fix
  ✔ ls-lint
✔ Applying modifications...

❌ 不符合预期:ls-lint git提交成功,没有检测到命名问题!

深入调试

为了找出原因,我创建了一个调试脚本来查看 lint-staged 实际传递给 ls-lint 的参数:

bash 复制代码
#!/bin/bash
# test-ls-lint.sh
echo "Arguments received: $@" >> /tmp/lint-staged-debug.log
echo "Number of arguments: $#" >> /tmp/lint-staged-debug.log
npx ls-lint "$@" 2>&1
exit $?

修改 package.json 临时使用这个脚本:

json 复制代码
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": ["eslint --fix", "./test-ls-lint.sh"]
  }
}

再次提交后查看日志:

bash 复制代码
$ cat /tmp/lint-staged-debug.log
Arguments received: /Users/jesse/Web/study/React/my-app/src/main.tsx
Number of arguments: 1

关键发现lint-staged 传递的是绝对路径,而不是相对路径!

验证假设

我分别测试了相对路径和绝对路径的情况:

bash 复制代码
# 测试 1:相对路径
$ npx ls-lint src/main.tsx
src/main.tsx failed for `.tsx` rules: pascalcase
$ echo $?
1  # 报错,符合预期

# 测试 2:绝对路径
$ npx ls-lint /Users/jesse/Web/study/React/my-app/src/main.tsx
$ echo $?
0  # 通过,不符合预期!

真相大白ls-lint 在处理绝对路径时,无法正确应用 .ls-lint.yml 中定义的命名规则,导致检测被静默绕过。

根本原因分析

经过分析和查阅 ls-lint 的文档,我发现:

  1. ls-lint 的设计初衷:它是一个基于项目结构的文件命名 lint 工具,需要根据文件相对于项目根目录的路径来应用规则。

  2. 绝对路径的问题:当传入绝对路径时,ls-lint 无法正确解析文件在项目中的相对位置,导致规则匹配失败或被忽略。

  3. lint-staged 的行为:默认情况下,lint-staged 会将暂存文件的绝对路径作为参数传递给命令,这是为了保证命令在任何工作目录下都能正确找到文件。

这就造成了一个矛盾:

  • lint-staged 传递绝对路径(为了保证可靠性)
  • ls-lint 需要相对路径(为了正确应用规则)

官方回应

这个问题已经被社区发现并报告给 ls-lint 开发团队。在 GitHub Issue #321 中,有开发者提出了相同的疑问。

好消息:ls-lint 开发团队已经确认了这个问题,并计划在 v2.4.0 版本中添加对绝对路径的支持。 但在 v2.4.0 发布之前,我们仍然需要使用本文提到的解决方案来确保文件名校验正常工作。

最终解决方案

经过多次尝试,我找到了最优雅的解决方案: 在 Husky 的 pre-commit 钩子中直接调用 ls-lint,并使用 git diff --staged --name-only 获取暂存文件的相对路径列表。

实现步骤

1. 从 lint-staged 配置中移除 ls-lint

修改 package.json,将 ls-lintlint-staged 配置中移除:

json 复制代码
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": ["eslint --fix"],
    "*.{css,scss}": "stylelint --fix"
  }
}

2. 在 pre-commit 钩子中添加 ls-lint 检查

编辑 .husky/pre-commit 文件,在执行 lint-staged 之前添加 ls-lint 检查:

bash 复制代码
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# 检查暂存文件的命名规范(使用相对路径)
pnpm exec ls-lint $(git diff --staged --name-only)

# 执行其他 lint-staged 检查
pnpm exec lint-staged

关键点

  • git diff --staged --name-only 返回的是相对路径 (如 src/main.tsx),这正是 ls-lint 所需要的
  • lint-staged 之前执行,可以尽早发现命名问题
  • 使用 pnpm exec 确保使用项目本地安装的 ls-lint 版本

验证效果

现在再次提交不符合命名规范的文件:

bash 复制代码
$ git add src/main.tsx
$ git commit -m "test"

src/main.tsx failed for `.tsx` rules: pascalcase
husky - pre-commit hook exited with code 1 (error)

✅ 成功拦截:现在可以正确检测到命名问题了!

提交符合规范的文件:

bash 复制代码
$ git add src/MainComponent.tsx
$ git commit -m "feat: add main component"

✔ Preparing...
✔ Running tasks...
✔ Applying modifications...

✅ 正常通过:符合规范的文件可以正常提交。

注意事项

1. 首次提交时的边界情况

如果是第一次提交(没有任何历史提交),git diff --staged --name-only 仍然可以正常工作,它会列出所有暂存的文件。

2. 空暂存区的处理

如果暂存区为空,git diff --staged --name-only 会返回空字符串,ls-lint 会自动跳过检查,不会报错。

3. 删除文件的处理

git diff --staged --name-only 也会包含被删除的文件。如果这些文件已经不存在,ls-lint 会忽略它们,不会影响检查结果。

4. 团队协作建议

  • 确保所有团队成员都安装了 Husky:pnpm exec husky install
  • 在项目的 README.md 中说明命名规范和检查机制
  • 考虑在 CI/CD 流程中也加入 ls-lint 检查,作为双重保障

总结

这次问题的根本原因是:lint-staged 传递绝对路径,而 ls-lint 需要相对路径才能正确应用规则。

最终的解决方案是:在 Husky 的 pre-commit 钩子中使用 git diff --staged --name-only 获取相对路径列表,然后直接传递给 ls-lint ,同时从 lint-staged 配置中移除 ls-lint

这个方案简洁、高效、可靠,完美解决了绝对路径导致的检测失效问题。希望这篇文章能帮助你避免类似的坑!

参考资料

相关推荐
ZZJsky1231 天前
我把每周都要手敲的发布分支,做成了一个 CLI
命令行
特立独行的猫a2 天前
使用 vcpkg 将 pngquant 命令行移植到鸿蒙 PC(OpenHarmony )
华为·harmonyos·命令行·vcpkg·pngquant·三方库·鸿蒙pc
电子科技圈3 天前
IAR作为Qt Group独立BU携两项重磅汽车电子应用开发方案首秀北京车展
开发语言·人工智能·汽车·软件工程·软件构建·代码规范·设计规范
栩栩云生3 天前
x-cmd v0.8.15: claw AI 助手升级,连微信、飞书更稳了;free 新增专家模式,直接把底层内存细节喂给你
github·agent·命令行
Fate_I_C4 天前
Android函数式编程代码规范文档
android·代码规范
Fate_I_C4 天前
Kotlin 协程:串行/并行请求、async/await、coroutineScope 管理并发、重试机制
android·代码规范
深海鱼在掘金5 天前
从Claude Code泄露源码看工程架构:第四章—— 一次请求的完整生命周期与流式执行引擎设计
人工智能·设计模式·命令行
深海鱼在掘金5 天前
从Claude Code泄露源码看工程架构:第二章——项目架构总览与分层设计哲学
人工智能·架构·命令行
就叫年华吧丶6 天前
Git Bash、CMD 与 PowerShell 的区别详解
linux·git·命令行·powershell·cmd·gitbash