尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Oxlint的anti-slop规则集:从能跑到能维护的前端代码质量治理

基于Oxlint的anti-slop规则集:从能跑到能维护的前端代码质量治理 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 oxlintlatest 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): PromiseUser { const response await api.getUser(/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会强制使用除非显式允许比较nullif (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 命名与注释上的 sloptypo 与空话注释命名上的 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/checkoutv4 - uses: actions/setup-nodev4 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 的价值会越来越明显。它解决的不是单条代码的语法错误而是整个团队在快速产出与可维护性之间的平衡问题。规则可以调整立场可以不统一但高速反馈、明确规则、持续清理这三件事比任何一条具体规则都重要。
返回列表