ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI 辅助前端代码审查:从 Lint 规则到语义级缺陷检测的工程实践(TaoToken 统一 Key 接入篇)

AI 辅助前端代码审查:从 Lint 规则到语义级缺陷检测的工程实践(TaoToken 统一 Key 接入篇) 1. 为什么 ESLint 全绿线上还是出 Bug如果你带过前端团队大概率遇到过这种场景CI 里 ESLint 零告警、TypeScript 编译通过、Prettier 格式统一代码合并上线结果第二天用户反馈「列表页点进详情再返回筛选条件丢了」。回头一查是useEffect依赖数组少写了一个filters闭包里读到的是旧值。ESLint 的react-hooks/exhaustive-deps当时确实没报——因为那个依赖是通过自定义 Hook 间接传进来的静态分析追不到。这就是传统 Lint 的能力边界。ESLint 的本质是 AST 模式匹配它能识别「变量声明了没用」「应该写」「key缺失」这类预定义语法模式但它不理解代码意图。一段类型完全正确、格式无可挑剔的代码完全可能藏着竞态条件、闭包陈旧值、状态直接修改、内存泄漏。这些缺陷的共同点是语法合法语义错误。AI 辅助代码审查要补的正是这一环。不是让大模型把 diff 读一遍随便点评而是把 ESLint 当基线过滤器只在静态规则覆盖不到的地方调用 LLM 做语义级缺陷检测。这篇就按这个思路从.eslintrc配置、审查脚本骨架、TaoToken 统一 Key 接入到本地跑通和 CI 验证给一条能直接抄的工程链路。适合已经在用 ESLint TypeScript、想把语义审查稳定接进流水线的前端团队。2. 前置准备TaoToken 统一 Key 与项目基线在写审查脚本之前先把两件事定下来静态基线要跑通模型通道要统一。静态基线这块不用多说你的项目里应该已经有 ESLint。如果没有先补上因为 AI 审查的输入之一就是 ESLint 的 JSON 输出用来做告警去重——静态能发现的问题不重复报给 LLM省 Token 也省延迟。模型通道这块我建议不要在每个脚本里散落各家 API Key。前端团队的审查脚本、本地调试、CI 环境往往要共用同一个模型入口散着放 Key 会导致轮换困难、额度对不上。TaoToken 提供的是统一 Key 和统一 API 通道一个 Key 走https://taotoken.net/api脚本里只认一个baseURL和一个apiKey换模型只改model字段。对 CI 来说这点很关键Secrets 里只存一个TAOTOKEN_API_KEY不用为每个供应商配一套。你需要准备的东西一个可用的 TaoToken API Key在控制台的 API Keys 页面创建地址是https://taotoken.net/console/api-keys创建后立刻复制页面刷新就不再完整显示。项目里已装好 ESLint能输出 JSON 格式报告。Node 18因为脚本里会用到原生fetch。注意Key 只放环境变量或 CI Secrets不要写进仓库。下面所有配置片段里的 Key 都用process.env.TAOTOKEN_API_KEY读取。3. 可复制配置.eslintrc 与审查脚本骨架3.1 ESLint 基线配置先给一份偏严格的.eslintrc.cjs重点是打开 Hooks 依赖检查和几类容易漏的规则。这份配置的作用是能静态抓的全抓走剩下的才交给 AI。// .eslintrc.cjs module.exports { root: true, env: { browser: true, es2022: true, node: true }, parser: typescript-eslint/parser, parserOptions: { ecmaVersion: latest, sourceType: module, ecmaFeatures: { jsx: true }, project: ./tsconfig.json, }, plugins: [react, react-hooks, typescript-eslint], extends: [ eslint:recommended, plugin:react/recommended, plugin:react-hooks/recommended, plugin:typescript-eslint/recommended, ], rules: { // Hooks 依赖静态能抓的尽量抓 react-hooks/exhaustive-deps: warn, react-hooks/rules-of-hooks: error, // 类型安全 typescript-eslint/no-explicit-any: warn, typescript-eslint/no-non-null-assertion: warn, // 常见逻辑坑 no-param-reassign: [warn, { props: true }], no-return-await: warn, require-await: warn, }, settings: { react: { version: detect } }, };跑一次确认能出 JSONnpx eslint . --ext .ts,.tsx --format json eslint-result.json || true|| true是故意的ESLint 有告警时退出码非零CI 里不希望它直接中断后面脚本会读 JSON 自己判断。3.2 审查脚本骨架脚本分三步收集 diff 上下文、组装 Prompt、调用模型并解析 JSON。先看上下文收集和 Prompt 组装。// scripts/ai-review.mjs import { execSync } from node:child_process; import { readFileSync, writeFileSync } from node:fs; const MAX_DIFF_LENGTH 15000; function getDiff() { const diff execSync(git diff origin/main...HEAD --unified5, { maxBuffer: 10 * 1024 * 1024, }).toString(); return diff.length MAX_DIFF_LENGTH ? diff.slice(0, MAX_DIFF_LENGTH) \n... [diff 已截断] : diff; } function getLintErrors() { try { const raw readFileSync(eslint-result.json, utf8); const results JSON.parse(raw); // 只保留 error/warning 的规则名和位置压缩体积 return results.flatMap((file) file.messages.map((m) ({ file: file.filePath, line: m.line, rule: m.ruleId, message: m.message, })) ); } catch { return []; } } const REVIEW_PROMPT 你是前端代码审查专家。只审查以下类别的语义级问题不要重复报告已有静态告警 1. React 生命周期useEffect 依赖缺失、闭包陈旧值、异步竞态 2. 状态管理直接修改 state、派生状态未缓存、非必要全局状态 3. 性能隐患内联对象/函数导致重渲染、大列表未虚拟化、未清理的订阅 4. 类型安全any 滥用、过度类型断言、泛型约束缺失 已有静态告警无需重复 {{LINT}} 代码变更 {{DIFF}} 严格按 JSON 输出不要输出多余文字 {issues:[{severity:error|warning|info,category:类别,location:文件:行号,description:问题,suggestion:修复建议}],summary:整体评价}; function buildPrompt(diff, lintErrors) { return REVIEW_PROMPT .replace({{LINT}}, JSON.stringify(lintErrors).slice(0, 3000)) .replace({{DIFF}}, diff); }3.3 TaoToken 调用与 settings.json 配置片段模型调用统一走 TaoToken 的 API 通道。下面这段是核心baseURL固定model按需换。// scripts/ai-review.mjs续 const API_BASE https://taotoken.net/api; const API_KEY process.env.TAOTOKEN_API_KEY; async function callModel(prompt) { const res await fetch(${API_BASE}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY}, }, body: JSON.stringify({ model: claude-sonnet-4-5, messages: [{ role: user, content: prompt }], temperature: 0.2, response_format: { type: json_object }, }), }); if (!res.ok) { throw new Error(模型调用失败: ${res.status} ${await res.text()}); } const data await res.json(); return JSON.parse(data.choices[0].message.content); } async function main() { const diff getDiff(); if (!diff.trim()) { console.log(无变更跳过语义审查); return; } const lintErrors getLintErrors(); const result await callModel(buildPrompt(diff, lintErrors)); writeFileSync(review-result.json, JSON.stringify(result, null, 2)); console.log(语义审查完成发现 ${result.issues.length} 个问题); } main().catch((e) { console.error(e); process.exit(1); });如果你用 Claude Code 或类似的编码 Agent 做本地审查可以在项目根目录放一份settings.json把模型通道指向同一个入口避免本地和 CI 两套配置{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: 从环境变量注入不要硬编码 }, model: claude-sonnet-4-5 }注意settings.json里的 Key 同样走环境变量注入。本地开发可以放在 shell profile 里CI 里放 Secrets。TaoToken 的 Coding Plan 适合长期跑编码 Agent 的场景如果团队每天有大量 PR 要审可以了解下https://taotoken.net/coding-plan。4. 本地跑通与 CI 验证4.1 本地验证先设 Key再跑脚本export TAOTOKEN_API_KEY你的Key npx eslint . --ext .ts,.tsx --format json eslint-result.json || true node scripts/ai-review.mjs cat review-result.json预期输出是一段 JSONissues数组里是语义级问题。如果 diff 为空会打印「无变更跳过语义审查」。第一次跑建议故意造一个闭包陷阱比如在useEffect里引用外部变量但不写进依赖看模型能不能抓出来。4.2 CI 集成GitHub Actions 里把上面两步串起来Key 从 Secrets 注入name: AI Code Review on: pull_request: types: [opened, synchronize] jobs: ai-review: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm ci - name: ESLint baseline run: npx eslint . --ext .ts,.tsx --format json eslint-result.json || true - name: AI semantic review env: TAOTOKEN_API_KEY: ${{ secrets.TAOTOKEN_API_KEY }} run: node scripts/ai-review.mjs - name: Upload result uses: actions/upload-artifactv4 with: name: review-result path: review-result.jsonfetch-depth: 0必须加否则git diff origin/main...HEAD拿不到完整历史diff 会算错。跑通后review-result.json会作为 artifact 上传后续可以接一个脚本把issues转成 PR Comment。4.3 成功结果长什么样一次正常的审查输出类似{ issues: [ { severity: warning, category: React 生命周期, location: src/pages/List.tsx:42, description: useEffect 内调用了 fetchData但依赖数组未包含 filters筛选变化时不会重新请求, suggestion: 将 filters 加入依赖数组或在 fetchData 内用 ref 读取最新值 } ], summary: 本次变更主要涉及列表筛选逻辑存在一处依赖缺失建议修复后合并 }看到这种结构化输出说明链路通了。接下来就是调 Prompt、调误报。5. 本篇常见错排查报 401 或 403Key 没读到。检查TAOTOKEN_API_KEY是否真的注入到运行环境CI 里 Secrets 名字是否拼错。本地echo $TAOTOKEN_API_KEY确认一下。报 404baseURL写错了。TaoToken 的 API 入口是https://taotoken.net/api注意不要多加或漏掉路径段。如果用的是 OpenAI 兼容格式路径是/v1/chat/completions。模型返回不是合法 JSONresponse_format有些模型不认或者 Prompt 里约束不够。把temperature降到 0.1并在 Prompt 末尾强调「只输出 JSON不要 markdown 代码块」。解析前可以先做一次content.replace(/json|/g, )兜底。diff 为空导致跳过CI 里最常见的原因是fetch-depth没设成 0或者分支名不是main。确认git diff origin/main...HEAD在 CI 环境能正常输出。Token 消耗过大检查MAX_DIFF_LENGTH和 lint 告警的截断。如果单个 PR 超过 2000 行 diff建议按文件拆分多次审查而不是硬塞进一次请求。静态告警只传规则名和位置不要传完整 message。误报太多先看是不是 Prompt 里类别太宽。把「性能隐患」这类主观性强的类别收窄只保留团队高频踩坑的几类。另外把 ESLint 已报的规则名在 Prompt 里明确列出模型就不会重复报。6. 把语义审查接进现有流水线落地路线其实就三步先用 ESLint TypeScript 把静态基线铺满确保格式、类型、命名这些规则明确的问题不浪费模型调用再针对团队真实踩过的语义级 Bug 设计 Prompt比如 Hooks 闭包、异步竞态、状态直接修改最后接进 CI配合人工反馈持续调误报率。成本控制的关键是「静态优先、AI 补位」。ESLint 能抓的绝不调模型只在静态规则力所不及的地方用 LLM 补意图推断。TaoToken 在这里的价值是通道统一——本地脚本、CI、编码 Agent 共用一个 Key 和一个baseURL换模型只改一个字段Secrets 只维护一份。如果你还没建 Key去https://taotoken.net/console/api-keys创建一个接入细节看https://taotoken.net/doc。想先验证模型对某段代码的判断准不准可以直接在https://taotoken.net/model-chat里贴代码试确认 Prompt 效果再写进脚本。长期跑编码 Agent 做审查的团队Coding Plan 那条线也值得看一眼。
返回列表