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

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

参考资料

相关推荐
柒和远方13 小时前
每日一学V017:用 Prompt 做 NLP:解构赋值与 AI 全栈的第一次实战
javascript·架构·代码规范
先吃饱再说2 天前
从 WeUI 按钮组件学 BEM 命名规范:让 CSS 不再难维护
前端·代码规范
XIAOHEZIcode2 天前
进程、会话与终端——一次真实的 Linux Session 解剖
linux·后端·命令行
好好风格3 天前
【一行代码】查看本机公网 IP
linux·命令行
这个DBA有点耶4 天前
COUNT进阶(续):超大表去重计数的极致优化
数据库·架构·代码规范
Darling噜啦啦4 天前
BEM 命名规范 + CSS Reset 实战:从微信按钮页面看专业前端开发
前端·css·代码规范
不要额外加糖4 天前
给 Codex 戴上紧箍, 治一治 AI 的过度发挥
前端·人工智能·代码规范
大大杰哥5 天前
Windows 批处理语法笔记:从入门到一键部署项目
脚本·命令行
野生技术架构师6 天前
Spec Coding 规范驱动编程实战:从 Vibe Coding 到 AI 代码规范
人工智能·代码规范
AllenSu06136 天前
ghostty配置(还原iterm2配色 and without starship)
命令行