anti-slop 这个名字听起来像是对 AI 生成内容的一次态度表达,但它落到前端工程里,实际是一套以 Oxlint 为基础的、有明确立场的 JavaScript/TypeScript 规则集合。它的目标不是替你把代码改得漂亮,而是把那些“能跑但经不起维护”的代码模式,在进入 review 或 CI 之前就拦下来。所谓 slop,指的是低信息密度、低质量、明显缺少思考痕迹的代码,常见于赶工、复制粘贴,以及大段依赖 AI 辅助生成的代码。这类代码并不一定报错,所以传统 lint 很少关注,但它会持续消耗团队的理解成本。
这篇文章不打算只给一个配置文件,而是把 anti-slop 从概念到落地讲清楚:代码里哪些模式属于 slop,为什么 Oxlint 适合承载这套规则,配置文件怎么设计,规则怎么和具体代码场景对应,以及接进 CI 后怎么处理大量存量告警。读完以后,你可以基于 Oxlint 搭出一套属于自己团队的 anti-slop 规则集,而不是照抄一份配置就结束。
1. anti-slop 到底在治理什么:从“能跑”到“能维护”
1.1 slop 代码的典型特征
“代码能跑”和“代码能维护”是两件事。slop 关注的恰恰是后者。一段代码即使测试全绿,也可能存在明显的学习成本:
- 类型不安全:到处是
any,函数签名完全无法表达输入输出约束。 - 错误处理是摆设:空
catch块,或者只打印一行没什么信息量的日志。 - 命名没有语义:
data、temp、obj、handler这类变量名反复出现。 - 条件永远是恒真或恒假:写代码时留下的临时判断没有清除。
- 注释和代码重复:注释把函数名翻译了一遍,没有解释约束、边界和为什么。
- 冗余表达式:不必要的三元、多余的
else、可以合并的布尔逻辑。
这些模式单拎出来都不会导致系统崩溃,但它们让阅读代码的人需要反复追问“这行在干嘛”。当代码量上到几十万行,这种追问成本会被放大。
1.2 为什么通用 lint 规则不够
传统 lint 规则的主要目标是正确性。no-undef防未定义变量,no-unreachable防不可达代码,这类规则解决的是“代码写错了”。而 anti-slop 处理的是“代码没写错,但写得很敷衍”。
这两类问题有一个重要区别:正确性问题有明确的对错,可以定义得非常机械;slop 问题大多是程度和审美问题,必须由团队达成一致后才能用规则表达。比如“不允许出现any”是一种强立场,它牺牲了写代码时的灵活性,换取了类型信息的完整性。普通 lint 配置不会主动做这种取舍,因为默认规则要尽量中立。
这也就是 anti-slop 强调“opinionated”的原因。它不是一份通用规则库,而是一份带着明确工程判断的规则集合。
1.3 anti-slop 的定位:一套有立场的规则集合
把 anti-slop 理解成一个插件可能不太准确。Oxlint 目前的自定义插件能力有限,更现实的做法是把它定位为一份可复用的.oxlintrc.json预设,或者一个 npm 包中的共享配置。它做的事情是:从 Oxlint 支持的 ESLint 规则、TypeScript 规则和 oxc 特定规则里,挑选并配置出一组能捕获 slop 模式的规则。
这套规则的价值不在于每一条都适合所有项目,而在于它提供了一个默认基线。团队可以先基于这个基线运行,再根据自己的技术栈和审美逐条调整。如果以后觉得某条规则误报太多,调成warn或者off都不丢人,这正是“opinionated”规则集应有的弹性。
2. 基于 Oxlint 落地 anti-slop,为什么是合理的选型
2.1 Oxlint 是什么:Rust 编写的 JS/TS lint 引擎
Oxlint 是 Oxc 项目生态中的 lint 工具,底层使用 Rust 实现,可以解析 JavaScript、TypeScript、JSX 等语法,并直接输出 lint 结果。它最大的特点是快,在大型前端仓库上往往能在几百毫秒到几秒内完成扫描,这是 Node.js 实现的 ESLint 很难做到的。
对于 anti-slop 这类规则集来说,速度不是加分项,而是前提。如果一套规则让本地保存后要等十秒才出结果,开发者第一件事就是关掉它。Oxlint 的快让 lint 能进入编辑器和 pre-commit 阶段,而不是只在 CI 里跑一次。
2.2 Oxlint 与 ESLint 的协作方式
很多项目已经有 ESLint 配置,Oxlint 的定位是补充而不是推翻。Oxlint 内置了一部分 ESLint 规则和 TypeScript ESLint 规则,因此在多数场景下可以直接替换日常检查。但 ESLint 的插件生态仍然更丰富,如果项目依赖某个只有 ESLint 才有的自定义插件,就需要评估兼容性。
常见的协作模式有两种:
- 小仓库直接用 Oxlint 作为唯一 lint 工具。
- 大仓库保留 ESLint 处理自定义规则和复杂插件,Oxlint 负责快速常见检查和 CI 门槛。
anti-slop 规则最好是放在 Oxlint 这种快工具里,让它成为高频检查的一部分。如果只能放在 ESLint 里,检查频率会明显下降。
2.3 Oxlint 规则体系:分类和插件边界
Oxlint 把规则按类别组织,常见的有correctness(正确性)、suspicious(可疑代码)、pedantic(严厉但不算错误)、restriction(禁用特性)、perf(性能)、style(风格)、nursery(试验性)。配置时可以直接控制整个类别,例如把correctness设为deny,把pedantic设为off。
规则名还体现了来源:
- 不带前缀或
eslint/:来自 ESLint 规则,例如no-empty。 typescript/:来自 TypeScript ESLint 的规则,例如typescript/no-explicit-any。oxc/:来自 Oxc 团队自己的规则,例如oxc/typo。react/、unicorn/等:对应插件的部分规则支持。
配置 anti-slop 时,最好先运行一次规则列表命令,确认目标版本里有哪些规则可用,避免写配置时想当然。
3. 搭建 anti-slop 规则集:环境与配置文件
3.1 安装和版本确认
在项目根目录执行:
npm init -y npm install --save-dev oxlint如果不想立刻安装,也可以临时用:
npx oxlint@latest src/安装完成后,先确认版本和可用规则,这一步很关键:
npx oxlint --version npx oxlint --rules--rules会输出当前版本支持的全部规则名,建议把输出保存下来,后续配置时对照使用。不同版本的规则覆盖范围会有差异,不要假设一条规则一定存在。
3.2 建立 .oxlintrc.json
在项目根目录创建.oxlintrc.json。下面是一个 anti-slop 风格的最小配置:
{ "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["typescript", "oxc"], "categories": { "correctness": "deny", "suspicious": "warn", "pedantic": "off", "perf": "warn", "style": "off", "restriction": "off" }, "rules": { "typescript/no-explicit-any": "warn", "typescript/no-non-null-assertion": "warn", "typescript/ban-ts-comment": "warn", "typescript/consistent-type-imports": "warn", "no-empty": "deny", "no-debugger": "deny", "no-console": "warn", "no-unused-vars": "deny", "no-unreachable": "deny", "no-undef": "deny", "eqeqeq": "deny", "no-constant-condition": "deny", "no-else-return": "warn", "no-unneeded-ternary": "warn", "no-duplicate-imports": "warn", "curly": "warn", "no-warning-comments": "warn", "oxc/typo": "warn" }, "ignorePatterns": ["dist/", "node_modules/", "build/", "coverage/"] }这个配置里correctness类别直接整体设为deny,因为正确性规则几乎没有讨论空间;suspicious先设为warn,避免一上来就阻塞;pedantic和restriction默认关闭,等团队确认后再按需放开。
3.3 理解 warn / deny / off 三种力度
Oxlint 对每条规则支持三种状态:
| 状态 | 含义 | 适用场景 |
|---|---|---|
deny | 违反时输出错误,可以作为 CI 失败条件 | 正确性、约定已经统一的规则 |
warn | 输出警告,不阻塞 | 刚推行、还没清理存量、团队有分歧的规则 |
off | 完全关闭 | 误报高、和项目风格冲突、暂时不值得投入的规则 |
anti-slop 的核心策略就是用deny守住底线,用warn慢慢推进。不要一开始就把所有规则设为deny,否则存量告警会淹没真正需要修的问题。
4. anti-slop 核心场景:代码示例与规则对照
4.1 类型上的 slop:显式 any 和非空断言
类型 slop 最典型的是函数返回any,或者参数直接写any。下面这个例子在代码 review 时很容易被忽略:
async function fetchUser(id: number): any { const response = await api.get(`/users/${id}`); return response.data; }调用方完全不知道返回结构,所有类型保护都失效。对应规则是typescript/no-explicit-any。修复方向是定义明确接口:
interface User { id: number; name: string; email: string; } async function fetchUser(id: number): Promise<User> { const response = await api.get<User>(`/users/${id}`); return response.data; }另一个类型 slop 是滥用非空断言!:
const el = document.querySelector('#root')!; el.innerHTML = '';如果#root不存在,这里会在运行时直接抛错。typescript/no-non-null-assertion会提醒你显式处理空值,而不是把风险往后推:
const el = document.querySelector('#root'); if (!el) { throw new Error('missing #root'); } el.innerHTML = '';4.2 异常处理上的 slop:空 catch 和吞错误
空catch是一切排查事故的源头:
try { await saveOrder(order); } catch (e) { // 暂时不处理 }no-empty会直接报错。但这里有个容易踩的坑:如果只在 catch 里写个注释,ESLint 风格的空块检查默认可能放行,因为注释让块不“空”。更稳妥的做法是把注释改成有实际动作的日志,或直接重抛:
try { await saveOrder(order); } catch (e) { logger.error('saveOrder failed', { orderId: order.id, error: e }); throw e; }即使团队选择吞掉异常,也应该吞得明确:记录原因、保留上下文、只在明确知道不会影响主流程的地方使用。
4.3 逻辑表达上的 slop:恒真条件、全等判断和冗余分支
临时调试留下的恒真条件非常典型:
if (isProduction() || true) { enableAnalytics(); }no-constant-condition会捕获这类问题。类似的还有直接写1 === 1、false等常量条件。
另一个常见 slop 是使用==而不是===。在 JavaScript 里0 == ''结果为true,这种隐式转换会制造很难查的 bug。eqeqeq会强制使用===,除非显式允许比较null:
if (user == null) { // 这里同时覆盖 null 和 undefined,是有意为之 }如果团队确认要允许这种写法,可以在配置里对eqeqeq做参数化设置,例如写成:
{ "rules": { "eqeqeq": ["deny", "smart"] } }smart模式只允许与null比较时使用==,其余情况仍然要求全等。
还有一种冗余逻辑是if (x) { return a; } else { return b; },这种else在return之后毫无必要。no-else-return会提示简化:
if (isReady) { return run(); } return wait();4.4 命名与注释上的 slop:typo 与空话注释
命名上的 slop 最容易被忽略,因为编译器完全不会检查语义。oxc/typo这类规则能发现常见拼写错误,例如把date写成dte,把button写成btun。虽然不能判断语义好坏,但至少能拦住最基础的拼写问题。
注释 slop 更隐蔽。像下面这种注释就是把函数名翻译了一遍:
// 获取用户信息的函数,从接口获取用户信息 async function fetchUserInfo(userId: string) { const response = await axios.get(`/api/user/${userId}`); return response.data; }这种注释没有提供函数名之外的任何信息,去掉反而更干净。如果希望规则帮忙提醒,no-warning-comments可以限制TODO、FIXME、XXX等标记长期残留。对于“注释没有信息量”这类问题,lint 规则很难完全覆盖,更多要依靠 code review 习惯。anti-slop 能做的,是把可以机械判断的部分先拦住。
4.5 AI 生成代码的 slop:规则能抓和抓不住的边界
随着 AI 辅助编码变常见,代码里的 slop 出现了新来源。AI 生成代码经常出现几类特征:
- 用了
any来绕过类型推导。 - 写了
catch (e) { console.log(e) }这种看起来在处理错误、实际上没有处理逻辑的代码。 - 重复导入同一种工具库,或者手动实现了一个库里已有的函数。
- 生成无意义注释和样板代码。
- 为了“稳妥”加上永远不可能触发或毫无意义的判断。
前两类可以用规则覆盖,后三类很多是语义层面的。比如no-unused-vars可以抓住导入但没用的工具函数,但“手动实现了一个已有函数”需要人工 review 才能发现。
所以 anti-slop 的边界要清楚:它降低高频低质量问题,但不会替代人工 review。实践中可以把规则提示作为 review 的第一层过滤,把 AI 生成代码的审查重点放在规则覆盖不到的语义层面。
5. 运行、接入 CI 与分级放量
5.1 本地执行和规则列表检查
配置好后,运行:
npx oxlint src/如果只想看当前配置改了哪些规则,或者确认某条规则是否存在,先跑:
npx oxlint --rules输出会很长,可以配合 grep 过滤:
npx oxlint --rules | grep no-explicit-any本地看到告警后,先用--fix处理可自动修复的部分:
npx oxlint --fix src/--fix之后再来一轮完整扫描,剩下的告警基本就是要人工改的逻辑问题。
5.2 npm scripts 和 lint-staged
把命令固化到package.json:
{ "scripts": { "lint": "oxlint", "lint:fix": "oxlint --fix", "lint:ci": "oxlint --deny-warnings" } }如果配合 lint-staged,在提交前只检查改动文件:
{ "lint-staged": { "*.{js,ts,jsx,tsx}": ["oxlint"] } }注意 lint-staged 默认会把文件路径传给命令,Oxlint 可以直接接收路径参数,不需要额外配置。
5.3 CI 中如何避免误报导致阻塞
在 CI 里跑npx oxlint --deny-warnings可以把所有 warning 提升为失败。但对于刚接入 anti-slop 的存量仓库,这一步会立刻导致构建失败。推荐分阶段:
- 第一阶段:本地和提交前运行,只输出 warning,不阻塞。
- 第二阶段:用一到两个迭代清理存量,把高频且无争议的规则改成
deny。 - 第三阶段:CI 中加入
--deny-warnings,把告警数量压到零。
GitHub Actions 示例:
name: lint on: push: pull_request: jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx oxlint --deny-warnings如果仓库较大,可以给 Oxlint 设置单独的 job,避免和构建流程耦合,也方便在告警多时快速定位。
5.4 从 warn 到 deny 的推行节奏
anti-slop 是 opinionated 的,但不代表要一步到位。更现实的节奏是:
- 第一周:配置以
warn为主,让团队熟悉规则含义。 - 第二周:根据告警频率挑出误报最少的规则升级为
deny,例如no-debugger、no-undef。 - 第三周:处理存量,把
no-empty、no-unused-vars等升级。 - 之后:每个迭代重新评估一次配置,删除长期无价值的规则。
千万不要因为某条规则“看起来很有道理”就直接铺到全仓库。先把影响面测出来,再决定力度。
6. 常见问题排查
6.1 规则名不存在或被自动忽略
现象:配置里写了某条规则,运行后没有任何提示,或者报 unknown rule。
原因:当前 Oxlint 版本不支持这条规则,或者规则名前缀写错。
检查方式:
npx oxlint --rules | grep rule-name解决方案:删除不存在的规则,或换用近似规则。Oxlint 版本升级后规则覆盖会变化,建议每次升级后重新跑一次--rules比对。
6.2 与 ESLint 规则重复或冲突
现象:项目同时使用 ESLint 和 Oxlint,同一条规则两边都报,或者两边判断结果不一致。
原因:Oxlint 对部分 ESLint 规则的实现存在细节差异,例如no-unused-vars对 TypeScript 接口的处理方式可能不同。
解决方案:明确分工。常见做法是 Oxlint 负责快速检查和 anti-slop 基线,ESLint 负责插件类和自定义规则。对于两边都支持的规则,选择一边开启,避免双重告警干扰。
6.3 存量仓库 Warning 数量爆炸
现象:接入后第一次运行出现几百个告警,无法判断从哪里开始改。
原因:规则集比项目原有规范严格,存量代码没有经过规则校验。
解决方案:先不接 CI 阻塞,按目录或按告警类型分批处理。可以先用 include 参数限制扫描范围:
npx oxlint src/modules/order把问题收敛到一个模块,再逐步扩大。也可以暂时把争议规则降为off,同时记录在配置注释里,等人力充足再开启。
6.4 大仓库性能与 ignore 配置
现象:扫描整个 monorepo 时虽然比 ESLint 快,但仍有几秒耗时,开发者觉得每次保存都卡。
原因:可能扫描了node_modules、构建产物或无关目录。
解决方案:在.oxlintrc.json里配置ignorePatterns:
{ "ignorePatterns": ["node_modules/", "dist/", "build/", "coverage/", "public/vendor/"] }也可以只在 lint-staged 里对改动文件运行,不必每次扫描全量。Oxlint 的优势在于即使全量扫描也足够快,但没必要扫描的目录还是应该排除。
7. 一份可复用的 anti-slop 配置和扩展建议
7.1 anti-slop 参考配置
下面这份配置可以作为团队引入 anti-slop 的起点。它刻意避免了过于苛刻的风格类规则,聚焦在类型、错误处理、逻辑和可读性上:
{ "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["typescript", "oxc"], "categories": { "correctness": "deny", "suspicious": "warn", "pedantic": "off", "perf": "warn", "style": "off", "restriction": "off" }, "rules": { "typescript/no-explicit-any": "warn", "typescript/no-non-null-assertion": "warn", "typescript/ban-ts-comment": "warn", "typescript/consistent-type-imports": "warn", "no-empty": "deny", "no-debugger": "deny", "no-console": "warn", "no-unused-vars": "deny", "no-unreachable": "deny", "no-undef": "deny", "eqeqeq": "deny", "no-constant-condition": "deny", "no-else-return": "warn", "no-unneeded-ternary": "warn", "no-duplicate-imports": "warn", "curly": "warn", "no-warning-comments": "warn", "oxc/typo": "warn" }, "ignorePatterns": ["dist/", "node_modules/", "build/", "coverage/"] }如果项目里 React 代码较多,可以加入react/插件,并考虑react/no-unstable-nested-components这类容易产生运行时反复渲染的规则。要注意的是,规则和插件是否可用以npx oxlint --rules输出为准。
7.2 落地前检查清单
接入 anti-slop 前,建议逐项确认:
- 已安装指定版本 Oxlint,并记录版本号。
- 已用
--rules核对配置中的每一条规则都真实存在。 - 已确定
deny、warn、off的划分依据。 - 已在
ignorePatterns中排除构建产物和依赖目录。 - 已在本地跑通
npx oxlint src/,并确认输出格式可以接受。 - 已决定和 ESLint 的分工边界,避免重复告警。
- 已设计从
warn到deny的推进节奏,而不是一次性全开。 - 已在 CI 中预留单独的 lint job,或至少是独立的 npm script。
- 已告诉团队成员如何查看告警、如何修复、如何提出规则调整建议。
这份清单的核心目的不是保证配置完美,而是保证出了问题能快速定位原因。
7.3 继续扩展方向
anti-slop 不是一份静态配置。团队稳定运行一段时间后,可以继续做几件事:
- 把配置发布成内部 npm 包,不同仓库共用一套 anti-slop 基线。
- 结合 code review 数据,把经常在 review 中出现的高频问题沉淀成新规则。
- 在 CI 中配合
--format github输出,让告警直接出现在 pull request 的 check 里。 - 定期运行
npx oxlint --rules比对升级后新增的规则,把新的高价值规则纳入配置。 - 把规则集按目录分级,核心业务代码使用严格档位,脚本和配置文件使用宽松档位。
对于正在被大模型辅助编码“喂养”的代码库,anti-slop 的价值会越来越明显。它解决的不是单条代码的语法错误,而是整个团队在快速产出与可维护性之间的平衡问题。规则可以调整,立场可以不统一,但高速反馈、明确规则、持续清理这三件事,比任何一条具体规则都重要。