AI 无视 lint 报错继续用废弃语法 —— 排查与修复

AI 生成的代码用了废弃 API,看到 lint 报错改了一处,下一个文件又把废弃写法塞回来。把禁令写进 .cursor/rules,并让 ESLint 在 CI 里硬性报 error。

你两年前就禁掉了 componentWillMount,ESLint 也标记它,代码库里早就零使用。Cursor 生成一个新组件,悄悄又用了 componentWillMount。你指出 lint 报错,AI 说”好的我马上修”,改完那个文件。三个 prompt 之后,在另一个完全无关的组件里,componentWillMount 又冒出来了。AI 看得到 lint 规则,也表示同意,然后在跨轮次时直接忘掉。同样的剧情在 var、ESM 项目里的 require()、React 16 风格的 useEffect 清理逻辑、pg.Pool.connect 回调而非 Promise 上一遍遍上演。模式很清楚:AI 训练数据里仍然充斥着废弃形式,lint 反馈只在当前轮次有效,你的护栏没有触达真正需要的地方。

最快的修法(先做这个): 把禁令写进 AI 每一轮都会读的项目规则文件,并让 ESLint 变成硬性 error 卡口。Cursor 用 .cursor/rules/conventions.mdc,frontmatter 设 alwaysApply: true(旧的单文件 .cursorrules 已废弃,截至 2026 年 6 月在 Agent 模式下会被静默忽略);Claude Code 用 CLAUDE.md。然后在 pre-commit hook 里强制 eslint . --max-warnings 0,这样只要废弃写法还在,构建就不可能通过。对话里的纠正不会持久,文件才会。

常见原因

按真实会话中出现频次排序。

1. AI 训练时见到的大量代码都还没废弃这个 API

GitHub 上公开代码里大部分还是旧写法。模型见过一百万次 componentWillMount,只见过一千次 componentDidMount,概率自然偏向旧的。

如何识别:在与你修过的位置毫无关联的其他文件里,同一个废弃写法反复出现。

2. lint 反馈只覆盖单次轮次

Cursor 或 Claude Code 读到 lint 输出,把那一行改掉,接着进入下一个任务——规则并没有进入长期记忆。

如何识别:同一条 lint 规则跨会话反复触发;“修复”只在同一对话内生效。

3. 项目层没有规则告诉 AI 什么是被禁的

你的项目规则文件(.cursor/rules/*.mdcCLAUDE.mdAGENTS.md)里压根没提到这条废弃,AI 没有任何信号说明这个代码库比公网更严格。2026 年有个很常见的变体:你把规则写在了根目录那个旧的 .cursorrules 里,但 Cursor 的 Agent 模式已经不再加载它,规则形同虚设。

如何识别:查规则文件——如果废弃模式没出现在里面,AI 就完全没有理由避开它。如果你唯一的规则文件就是 .cursorrules,那本身就是 bug:把它迁移到 .cursor/rules/

4. ESLint 把规则设为 “warn” 而不是 “error”

规则存在,但只 warn 不 error。AI 跑完 lint 看到”通过了”,其实是被警告淹没了。

如何识别:eslint --max-warnings 0 失败,但单跑 eslint 退出码是 0。

5. 废弃是项目自定义规则,AI 无从知晓

你用自定义规则禁了生产代码里的 console.log。AI 要等到跑 lint 才知道有这条规则,即便看到了也未必理解为什么禁。

如何识别:lint 输出里的规则名,在 AI 的解释里从来没被提到过。

6. 库升级后悄悄废弃了旧写法

你把 pg 从 v7 升到 v8,回调写法被废弃。AI 训练数据 v7 和 v8 各占一半,挑哪个全看”感觉”。

如何识别:npm ls <package> 显示是较新的主版本;废弃写法对应的是上一个主版本。

你属于哪一种

症状最可能的原因最快的修法
废弃写法在全新的、无关的文件里冒出来#1 训练偏向 / #3 没有项目规则.cursor/rules/*.mdc(alwaysApply: true)或 CLAUDE.md 里加禁令
规则写下来了,但 Cursor Agent 模式仍然无视#3,用的是旧 .cursorrules把规则搬进 .cursor/rules/conventions.mdc
eslint 退出码是 0,但废弃写法明明在那#4 规则停在 warn 或被禁用把规则设为 "error";跑 eslint . --max-warnings 0
修复在对话里管用,新会话又坏#2 lint 反馈只覆盖单轮把 lint 做成 pre-commit 卡口,而不是对话提醒
lint 报了一个 AI 从没提过的规则名#5 自定义/项目专属规则在规则文件里写明规则及其原因
旧写法在依赖升级后立刻出现#6 库升级npm ls <pkg>;在 src/ 上跑 codemod

开始之前

  • 把当前在意的所有废弃模式列清楚,写下来:“我们永远不用 X,总是用 Y”。
  • 跑一遍 eslint . --max-warnings 0,确认你的规则确实按 error 而非 warning 在执行。
  • 确认 AI 真正读的是哪个规则文件:Cursor Agent 模式读 .cursor/rules/*.mdc,Claude Code 读 CLAUDE.md,跨工具标准是 AGENTS.md。注意你是不是还在依赖旧的根目录 .cursorrules(截至 2026 年 6 月,Cursor Agent 模式已不再读它)。
  • 抓住一个 AI 重新引入废弃写法的实例,保存文件名和前后 prompt。

需要收集的信息

  • 废弃模式(如 componentWillMountvarrequire()、回调 API)。
  • 替代写法(正确做法)。
  • 应该触发的 lint 规则名(react/no-deprecatedno-var 等)。
  • 该规则在 eslint.config.js(flat config)中的启用状态与严重度。ESLint 10(2026 年 2 月发布)已彻底移除旧的 .eslintrc 系列格式。
  • .cursor/rules/*.mdc / CLAUDE.md / AGENTS.md 的内容。
  • 如果废弃和库相关,库的版本号。

分步修复

按从快速见效到长期策略排序。

第 1 步:把 lint 规则改成硬性 error

用 flat config 的 eslint.config.js。ESLint 10(2026 年 2 月)已移除对 .eslintrc.* 的支持,所以如果你还在用 .eslintrc,要么是被钉在 ESLint 9(2026-08-06 停止维护),要么配置压根没被读取:

// eslint.config.js
export default [
  {
    rules: {
      "react/no-deprecated": "error",
      "no-var": "error",
      "import/no-commonjs": "error",
    },
  },
];

然后在 CI 和本地都强制零警告——注意要显式带上 . 目标,flat config 需要它:

eslint . --max-warnings 0

这样 AI 那句”我跑了 lint,通过了”就真的会在废弃写法上失败。(warn 级别的规则不会改变退出码 0,AI 仍然以为自己干完了——所以严重度必须是 error。)

第 2 步:把禁止项写进 AI 真正会读的规则文件

格式取决于工具,而写错文件正是禁令触达不到模型的头号原因:

  • Cursor Agent 模式:建 .cursor/rules/conventions.mdc。根目录的 .cursorrules 出于向后兼容仍然存在,但截至 2026 年 6 月在 Agent 模式下会被忽略,别再依赖它。.mdc 文件需要 frontmatter,设 alwaysApply: true 让规则每一轮都加载,而不是只在 Cursor 判断”相关”时才加载。
  • Claude Code:用仓库根目录的 CLAUDE.md(Claude Code 也读 AGENTS.md)。
  • 跨工具(Codex、Copilot、Gemini CLI 等):用 AGENTS.md,这是 28+ 个 agent 都读的共享标准。

Cursor 规则文件长这样——顶部那段 frontmatter 是必需的:

---
description: Banned legacy patterns this codebase rejects
globs: src/**/*.{ts,tsx,js,jsx}
alwaysApply: true
---

## Banned patterns (do NOT generate)

- componentWillMount, componentWillReceiveProps, componentWillUpdate
  -> use componentDidMount + useEffect equivalents
- var -> use const, let
- require(), module.exports -> use ESM import / export
- pg client callback API -> use the promise / async-await form
- console.log in src/ outside of src/lib/logger.ts

If you generate any of the above, the lint check WILL fail.
Verify your code does not contain these patterns before responding.

CLAUDE.md / AGENTS.md 用同样的正文,纯 Markdown 即可(不需要 frontmatter)。这能把禁止项注入到 AI 每一轮的工作上下文里。

第 3 步:用正例告诉 AI 正确写法长什么样

加一份 docs/conventions.md,左右对照,然后在规则文件里引用它:

# Banned vs preferred

## React lifecycle

WRONG:
  componentWillMount() { this.fetch(); }

RIGHT:
  useEffect(() => { fetch(); }, []);

## Module system

WRONG:
  const x = require("foo");

RIGHT:
  import x from "foo";

具体对照比抽象禁令有效得多——AI 会模仿 RIGHT 那边的样式。

第 4 步:把 lint 放进 pre-commit hook

package.json:

{
  "scripts": {
    "lint": "eslint . --max-warnings 0"
  },
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": "eslint --max-warnings 0"
  }
}

配 husky / lefthook 等 pre-commit hook。提交时的强制校验 AI 绕不过去。

第 5 步:能 --fix 的就自动修

很多废弃规则自带 autofix(no-varprefer-constprefer-arrow-callback 等):

eslint . --fix

每次 AI 生成完跑一遍,免去人工抓回归的认知负担。

第 6 步:大范围迁移用 codemod

回调转 Promise、class 转 hooks、CommonJS 转 ESM 这种大动作,用 jscodeshiftts-morph:

npx jscodeshift -t ./codemods/cb-to-promise.js src/

codemod 跑完后,代码库里废弃写法的样本归零——AI 对你代码库做 RAG 时,只看得到新写法,自然会模仿新风格。

验证

  • eslint . --max-warnings 0 在整个仓库零警告退出。
  • 让 AI 在 3 个不同 prompt 下生成新文件——没有一个再引入废弃写法。
  • grep -r "<deprecated-pattern>" src/ 全仓零命中。
  • 故意提交一段废弃写法,pre-commit hook 用清晰错误信息拦下。

长期预防

  • .cursor/rules/*.mdc / CLAUDE.md / AGENTS.md 里维护”deprecations”小节,每次升级主版本依赖时回顾。
  • ESLint、TSC、自定义 AST linter 对废弃 API 统一设为 error——warning 是 AI 会忽略的训练噪声。
  • 每条禁令都配上正确替代写法,不要光禁不教。AI 对”这样写”远比对”别那样写”敏感。
  • 定期 grep -r "<deprecated>" src/ 清理漏网之鱼;AI 工具非常忠实地模仿仓库现状。
  • 升级主版本时,专门拉一个 PR 用 codemod 把废弃 API 在整个仓库扫一遍。混合状态既迷惑人也迷惑 AI。
  • 简要写下每条废弃的”为什么”。AI 对”componentWillMount 在并发模式下不安全”的锚定效果,远好于一条干巴巴的禁令。

常见误区

  • 把规则停在 “warn” 级别。AI 看到 lint 通过就以为自己干完了。
  • 把禁令写在根目录 .cursorrules 里,还以为 Cursor 会读。Agent 模式无视它,搬到 .cursor/rules/*.mdc
  • 升级到 ESLint 10 后还留着 .eslintrc——它已不再被读取,你所有规则都不会触发。迁移到 eslint.config.js
  • 禁令只写进自己的笔记,而不是 AI 真正会读的规则文件。
  • 在一个文件里修了废弃写法,却没 grep 整个仓库——通常还埋着 5 到 10 处。
  • 以为 AI 会从本次对话内的纠正里”学到”,于是不写规则文件。跨会话不持久,只对同一对话的后续轮次有效。
  • 同一个文件里新旧风格混杂。AI 会挑离它最近的那种风格沿用下去。

相关问题见 AI 建议了过时的依赖AI 引入的 TypeScript 错误Claude Code 缺失项目上下文。权威格式参见 Cursor Rules 官方文档ESLint 配置文件文档

FAQ

Q:我一直在纠正它,它一直犯同样的错,是我哪里没做对吗?

纠正只在当前对话有效。把规则搬到 .cursor/rules/*.mdc(Cursor)或 CLAUDE.md(Claude Code),新会话才会继承。对话内提醒只是短期记忆。

Q:我的 .cursorrules 文件里已经写了禁令,为什么 Cursor 还是无视?

截至 2026 年 6 月,根目录那个旧的 .cursorrules 已废弃,在 Cursor 的 Agent 模式下不会被加载——只要切到 agent,整个文件就停止生效。把它迁移到 .cursor/rules/conventions.mdc,frontmatter 设 alwaysApply: true。你可以为更老的非 agent 流程保留 .cursorrules,但别再依赖它。

Q:lint 规则已经启用了,AI 还是生成废弃写法,为什么?

AI 是”先生成 → 再跑 lint → 再修当前轮次的文件”的流程。如果你在 lint 跑之前就接受了代码,或之后才手动 lint,废弃形式就溜走了。把 lint 做成 pre-commit 硬卡口。

Q:我直接把废弃 API 从库里删掉行不行?

如果库由你掌控,这是最干净的做法。第三方库就不行了,只能靠规则文件 + lint 强制。

Q:怎么让 AI 主动推荐现代写法,而不是只是不用旧的?

每条禁令都配上一个具体的 RIGHT 示例。AI 模仿正面样本的能力远强于尊重抽象禁令。

标签: #排查 #AI 编程 #deprecation #eslint #linting