# 静态分析适配器优化设计书(AI 编码用) > 面向 AI 编码助手的技术实现规格。覆盖三种配置模式优先级机制、侧边栏适配器配置面板、外部依赖检测、配置文件模板生成等全部优化项。包含 TypeScript 接口定义、方法签名、文件级变更规格和实现伪代码。 - **项目**: vscode-code-reviewer (Code Purifier) - **分支**: vscode-code-reviewer - **日期**: 2026-07-27 - **版本**: 1.0.1 → 1.1.0 --- ## 目录 - [01 设计目标与范围](#01-设计目标与范围) - [02 现有架构分析](#02-现有架构分析) - [03 配置模式优先级机制](#03-配置模式优先级机制) - [04 数据结构设计](#04-数据结构设计) - [05 配置项 Schema 变更](#05-配置项-schema-变更) - [06 状态检测逻辑](#06-状态检测逻辑) - [07 侧边栏面板实现](#07-侧边栏面板实现) - [08 文件级变更规格](#08-文件级变更规格) - [09 默认配置模板](#09-默认配置模板) - [10 测试要点](#10-测试要点) --- ## 01 设计目标与范围 本次优化在不破坏现有代码审查流程的前提下,为四个静态分析适配器(ESLint、Stylelint、PMD、SQL-Lint)增加统一的三层配置模式机制,并在侧边栏设置面板中新增"静态分析适配器"可视化配置区域。用户无需查阅外部文档即可完成全部适配器配置。 ### 优化项清单 | # | 优化项 | 涉及文件 | 变更类型 | |---|--------|----------|----------| | 1 | 三层配置模式优先级(全局 > 项目级 > 内置) | `config/linter.ts`、各适配器文件 | 新增逻辑 | | 2 | 适配器启用/禁用开关 | `package.json`、`config/linter.ts`、`orchestrator/*` | 新增配置 + 逻辑 | | 3 | 侧边栏适配器配置面板(4 张卡片) | `views/setupView.ts`、`views/setupView.js` | 新增 UI + 逻辑 | | 4 | 配置模式自动检测 | `views/setupView.ts` | 新增方法 | | 5 | 外部依赖检测(Java / Python+sqlfluff) | `views/setupView.ts` | 新增方法 | | 6 | 配置文件模板创建与打开 | `views/setupView.ts` | 新增方法 | | 7 | 配置变更实时刷新 | `views/setupView.ts` | 新增监听器 | | 8 | ESLint / Stylelint 自定义配置路径支持 | `package.json`、`config/linter.ts`、`adapters/eslint.ts`、`adapters/stylelint.ts` | 新增配置 + 逻辑 | > **零破坏性原则**:所有变更均为新增或追加,不修改任何现有逻辑分支。现有的 AI 配置、规则文件导入、连接测试、代码审查命令等功能完全不受影响。适配器卡片列表由独立的 `
` 容器渲染,即使渲染函数出错也不影响其他区域。 --- ## 02 现有架构分析 ### 源码目录结构 ``` src/ ├── activation/ # 插件激活逻辑 ├── adapters/ # 静态分析适配器 │ ├── adapter.ts # 类型 re-export │ ├── eslint.ts # ESLint 适配器 │ ├── stylelint.ts # Stylelint 适配器 │ ├── pmd.ts # PMD 适配器 │ ├── sql-lint.ts # SQL-Lint 适配器 │ └── jsp.ts # JSP 适配器 ├── ai/ # AI 审查引擎 ├── config/ # 配置读取层 │ ├── index.ts │ ├── ai.ts │ ├── linter.ts # ← 本次重点变更 │ ├── fixer.ts │ └── secret.ts ├── orchestrator/ # 审查编排器(调度适配器) ├── views/ │ ├── setupView.ts # ← 本次重点变更 │ └── setupView.js # ← 本次重点变更 ├── panel/ # 审查结果面板 ├── types.ts # 核心类型定义 ├── extension.ts # 插件入口 └── ... ``` ### 现有核心类型(src/types.ts) ```typescript import * as vscode from 'vscode'; export type Severity = 'error' | 'warning' | 'info'; export type AdapterStatus = 'ok' | 'tool-unavailable' | 'execution-failed'; export interface LinterDiagnostic { severity: Severity; ruleId: string; message: string; range: vscode.Range; suggestion?: string; } export interface AdapterResult { diagnostics: LinterDiagnostic[]; status: AdapterStatus; errorMessage?: string; } export interface LinterAdapter { id: string; supportedLanguages: string[]; check(document: vscode.TextDocument, workingDir: string): Promise; isAvailable(): boolean; } ``` ### 现有配置读取层(src/config/linter.ts) ```typescript import * as vscode from 'vscode'; const ROOT = 'vscode-code-reviewer'; export function getLinterForLanguage(language: string): string { return vscode.workspace.getConfiguration(ROOT).get(`linters.${language}`, ''); } export function getPmdJarPath(): string { return vscode.workspace.getConfiguration(ROOT).get('pmd.jarPath', ''); } export function getPmdRulesetPath(): string { return vscode.workspace.getConfiguration(ROOT).get('pmd.rulesetPath', ''); } export function getPmdJspRulesetPath(): string { return vscode.workspace.getConfiguration(ROOT).get('pmd.jspRulesetPath', ''); } export function getSqlLintConfigFile(): string { return vscode.workspace.getConfiguration(ROOT).get('sql-lint.configFile', ''); } ``` ### 现有 package.json 配置项(相关部分) | 配置键 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `linters.javascript` | string (enum) | `"eslint"` | JS 语言的 linter 选择 | | `linters.typescript` | string (enum) | `"eslint"` | TS 语言的 linter 选择 | | `linters.java` | string (enum) | `"pmd"` | Java 语言的 linter 选择 | | `linters.css` | string (enum) | `"stylelint"` | CSS 语言的 linter 选择 | | `linters.sql` | string (enum) | `"sql-lint"` | SQL 语言的 linter 选择 | | `pmd.jarPath` | string | `""` | PMD jar 目录路径 | | `pmd.rulesetPath` | string | `""` | PMD 规则集 XML 路径 | | `pmd.jspRulesetPath` | string | `""` | JSP 规则集 XML 路径 | | `sql-lint.configFile` | string | `""` | sqlfluff 配置文件路径 | > **现有缺口**:ESLint 和 Stylelint 适配器目前没有自定义配置路径的设置项(只有 `linters.javascript` 等语言级 linter 选择开关),无法通过 VS Code Settings 指定自定义 ESLint/Stylelint 配置文件路径。也没有适配器级别的启用/禁用开关。 --- ## 03 配置模式优先级机制 每个适配器支持三种配置模式,优先级从高到低。高优先级配置存在时,低优先级配置自动忽略。 | 优先级 | 模式名称 | 配置来源 | 徽章标识 | |--------|----------|----------|----------| | **最高** | 全局配置(VS Code Settings) | `linters.eslintConfigPath` / `linters.stylelintConfigPath` / `pmd.rulesetPath` / `sql-lint.configFile` 等设置项 | 全局配置 | | **中** | 项目级配置 | 项目根目录下的配置文件(`.eslintrc.*` / `.stylelintrc.*` / `ruleset.xml` / `.sqlfluff`) | 项目配置 | | **最低** | 内置规则(零配置) | 插件打包的内置规则集 | 内置规则 | ### 各适配器的配置文件探测列表 `detectConfigMode()` 方法需要按以下列表在项目根目录探测文件是否存在: | 适配器 | 项目级配置文件名(按探测顺序) | VS Code Settings 键 | |--------|-------------------------------|---------------------| | **ESLint** | `.eslintrc.js` → `.eslintrc.json` → `.eslintrc.yaml` → `.eslintrc.yml` → `.eslintrc` → `eslint.config.js` → `eslint.config.mjs` | `linters.eslintConfigPath` | | **Stylelint** | `.stylelintrc.js` → `.stylelintrc.json` → `.stylelintrc.yaml` → `.stylelintrc.yml` → `.stylelintrc` → `stylelint.config.js` | `linters.stylelintConfigPath` | | **PMD** | `ruleset.xml` | `pmd.rulesetPath` | | **SQL-Lint** | `.sqlfluff` | `sql-lint.configFile` | ### PMD 特殊处理:双配置项 PMD 适配器有两类独立配置项:**规则集(rulesetPath)**和**运行时(jarPath)**。两者独立配置,组合关系如下: | jarPath | rulesetPath | 结果 | |---------|-------------|------| | 空 | 空 | 内置引擎 + 内置规则集 | | 空 | 有值 | 内置引擎 + 自定义规则集 | | 有值 | 空 | 自定义引擎 + 内置规则集 | | 有值 | 有值 | 自定义引擎 + 自定义规则集 | > **jarPath 回退规则**:`jarPath` 指向的目录中必须存在 `PmdRunner.class` 文件,否则插件回退到内置 PMD。如果只设置了 `jarPath` 而没设置 `rulesetPath`,规则集仍使用内置的 `pmd-java-ruleset.xml`。 --- ## 04 数据结构设计 ### 新增类型定义 以下接口需添加到 `src/types.ts` 或 `src/views/setupView.ts` 顶部(推荐放在 setupView.ts 内部,因为仅该文件使用): ```typescript /** 配置模式枚举 */ export type ConfigMode = 'builtin' | 'project' | 'global'; /** 外部依赖就绪状态 */ export type DependencyStatus = 'ready' | 'missing' | 'none'; /** 单个适配器的配置面板状态 */ export interface AdapterConfigStatus { /** 适配器唯一标识 */ id: string; /** 显示名称(PMD / SQL-Lint / ESLint / Stylelint) */ name: string; /** 是否已启用 */ enabled: boolean; /** 当前生效的配置模式 */ configMode: ConfigMode; /** 外部依赖状态(ESLint/Stylelint 为 'none') */ dependencyStatus: DependencyStatus; /** 外部依赖显示文本(如 "Java"、"Python + sqlfluff") */ dependencyLabel?: string; /** 配置是否已完成(全局或项目级配置存在时为 true) */ configured: boolean; /** 面板显示的操作指南文本 */ guideText: string; /** 项目级配置文件名(用于"配置文件"按钮创建/打开) */ projectConfigFileName: string; /** VS Code Settings 跳转目标键 */ settingsTarget: string; } /** 侧边栏消息:适配器操作类型 */ export type AdapterMessageAction = | 'openAdapterConfig' | 'openSettings' | 'toggleAdapter'; /** 侧边栏消息:适配器操作载荷 */ export interface AdapterMessage { action: AdapterMessageAction; adapterId: string; } ``` ### 适配器元数据常量表 在 `setupView.ts` 中定义一个静态常量表,描述四个适配器的元信息,供 `collectAdapterStatus()` 使用: ```typescript const ADAPTER_METADATA: Record = { pmd: { name: 'PMD', projectConfigFileName: 'ruleset.xml', settingsTarget: 'vscode-code-reviewer.pmd', guideText: '需要 Java 运行环境;项目根目录创建 ruleset.xml 或在设置中配置 pmd.rulesetPath', hasExternalDependency: true, dependencyLabel: 'Java', configFileTemplate: PMD_RULESET_TEMPLATE, }, 'sql-lint': { name: 'SQL-Lint', projectConfigFileName: '.sqlfluff', settingsTarget: 'vscode-code-reviewer.sql-lint', guideText: '需要 Python 环境和 sqlfluff;运行 pip install sqlfluff,项目根目录创建 .sqlfluff', hasExternalDependency: true, dependencyLabel: 'Python + sqlfluff', configFileTemplate: SQLFLUFF_TEMPLATE, }, eslint: { name: 'ESLint', projectConfigFileName: '.eslintrc.js', settingsTarget: 'vscode-code-reviewer.linters', guideText: '项目根目录创建 .eslintrc.js 或在 VS Code 设置中配置 eslintConfigPath', hasExternalDependency: false, configFileTemplate: ESLINT_TEMPLATE, }, stylelint: { name: 'Stylelint', projectConfigFileName: '.stylelintrc.js', settingsTarget: 'vscode-code-reviewer.linters', guideText: '项目根目录创建 .stylelintrc 或在 VS Code 设置中配置 stylelintConfigPath', hasExternalDependency: false, configFileTemplate: STYLELINT_TEMPLATE, }, }; ``` --- ## 05 配置项 Schema 变更 ### package.json — 新增配置项 在 `contributes.configuration.properties` 中追加以下配置项: ```jsonc // ESLint 自定义配置路径 "vscode-code-reviewer.linters.eslintConfigPath": { "type": "string", "default": "", "description": "ESLint 自定义配置文件路径(绝对路径)。留空则使用项目 .eslintrc 或内置规则" } // Stylelint 自定义配置路径 "vscode-code-reviewer.linters.stylelintConfigPath": { "type": "string", "default": "", "description": "Stylelint 自定义配置文件路径(绝对路径)。留空则使用项目 .stylelintrc 或内置规则" } // 适配器启用/禁用开关(4 项) "vscode-code-reviewer.linter.pmd.enabled": { "type": "boolean", "default": true, "description": "启用/禁用 PMD 适配器" } "vscode-code-reviewer.linter.sql-lint.enabled": { "type": "boolean", "default": true, "description": "启用/禁用 SQL-Lint 适配器" } "vscode-code-reviewer.linter.eslint.enabled": { "type": "boolean", "default": true, "description": "启用/禁用 ESLint 适配器" } "vscode-code-reviewer.linter.stylelint.enabled": { "type": "boolean", "default": true, "description": "启用/禁用 Stylelint 适配器" } ``` > **命名空间注意**:现有配置使用 `linters.*`(复数)作为语言级 linter 选择,`pmd.*` / `sql-lint.*` 作为各适配器的独立配置。新增的启用/禁用开关统一放在 `linter.*`(单数)命名空间下,避免与现有 `linters.*` 冲突。ESLint 和 Stylelint 的自定义配置路径放在 `linters.*` 下,与现有 `linters.javascript` 等保持同级。 ### config/linter.ts — 新增读取函数 ```typescript /** 获取 ESLint 自定义配置路径 */ export function getEslintConfigPath(): string { return vscode.workspace.getConfiguration(ROOT).get('linters.eslintConfigPath', ''); } /** 获取 Stylelint 自定义配置路径 */ export function getStylelintConfigPath(): string { return vscode.workspace.getConfiguration(ROOT).get('linters.stylelintConfigPath', ''); } /** 获取适配器启用状态 */ export function isAdapterEnabled(adapterId: string): boolean { return vscode.workspace.getConfiguration(ROOT).get(`linter.${adapterId}.enabled`, true); } /** 设置适配器启用状态(写入 Global Settings) */ export async function setAdapterEnabled(adapterId: string, enabled: boolean): Promise { await vscode.workspace.getConfiguration(ROOT).update( `linter.${adapterId}.enabled`, enabled, vscode.ConfigurationTarget.Global ); } ``` ### 适配器集成:启用/禁用检查 在 `orchestrator/` 中调度适配器前,需检查该适配器是否已启用。在调用 `adapter.check()` 之前添加守卫: ```typescript import { isAdapterEnabled } from '../config/linter'; // 在 orchestrator 的适配器调度循环中 for (const adapter of adapters) { if (!isAdapterEnabled(adapter.id)) { continue; // 跳过已禁用的适配器 } if (!adapter.isAvailable()) { continue; // 跳过不可用的适配器 } const result = await adapter.check(document, workingDir); // ... 处理结果 } ``` --- ## 06 状态检测逻辑 ### detectConfigMode() — 配置模式检测 该方法检测指定适配器当前生效的配置模式。优先检查全局配置,其次项目级配置,最后回退到内置。 ```typescript import * as path from 'path'; import * as fs from 'fs'; import * as vscode from 'vscode'; import { getEslintConfigPath, getStylelintConfigPath, getPmdRulesetPath, getSqlLintConfigFile } from '../config/linter'; /** 各适配器项目级配置文件探测列表 */ const PROJECT_CONFIG_FILES: Record = { eslint: ['.eslintrc.js', '.eslintrc.json', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc', 'eslint.config.js', 'eslint.config.mjs'], stylelint: ['.stylelintrc.js', '.stylelintrc.json', '.stylelintrc.yaml', '.stylelintrc.yml', '.stylelintrc', 'stylelint.config.js'], pmd: ['ruleset.xml'], 'sql-lint': ['.sqlfluff'], }; /** 各适配器全局配置路径读取函数 */ const GLOBAL_CONFIG_GETTERS: Record string> = { eslint: getEslintConfigPath, stylelint: getStylelintConfigPath, pmd: getPmdRulesetPath, 'sql-lint': getSqlLintConfigFile, }; function detectConfigMode(adapterId: string): ConfigMode { // 1. 检查全局配置(VS Code Settings) const globalPath = GLOBAL_CONFIG_GETTERS[adapterId]?.(); if (globalPath && globalPath.trim() !== '') { return 'global'; } // 2. 检查项目级配置文件 const workspaceFolders = vscode.workspace.workspaceFolders; if (workspaceFolders && workspaceFolders.length > 0) { const rootPath = workspaceFolders[0].uri.fsPath; const configFiles = PROJECT_CONFIG_FILES[adapterId] ?? []; for (const fileName of configFiles) { const filePath = path.join(rootPath, fileName); if (fs.existsSync(filePath)) { return 'project'; } } } // 3. 回退到内置 return 'builtin'; } ``` ### checkJavaReady() — Java 环境检测 通过执行 `java -version` 检测 Java 运行环境是否可用。使用 `child_process.execSync` 同步执行,捕获 stderr 输出(Java 版本信息输出到 stderr)。 ```typescript import { execSync } from 'child_process'; function checkJavaReady(): boolean { try { const output = execSync('java -version', { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], }); return true; } catch { // java -version 输出到 stderr,execSync 会因非零退出码抛错 // 但即使版本信息在 stderr 中,只要命令存在就算就绪 try { const result = execSync('java -version 2>&1', { encoding: 'utf-8', timeout: 5000, }); return result.includes('version'); } catch { return false; } } } ``` ### checkPythonReady() — Python + sqlfluff 检测 先检测 Python(尝试 `python3` 和 `python`),再检测 `sqlfluff` 命令是否可用。 ```typescript function checkPythonReady(): boolean { // 1. 检测 Python let pythonCmd = ''; for (const cmd of ['python3', 'python']) { try { execSync(`${cmd} --version`, { encoding: 'utf-8', timeout: 5000, stdio: 'pipe' }); pythonCmd = cmd; break; } catch { continue; } } if (!pythonCmd) return false; // 2. 检测 sqlfluff try { execSync('sqlfluff --version', { encoding: 'utf-8', timeout: 5000, stdio: 'pipe' }); return true; } catch { return false; } } ``` > **依赖检测注意事项**:依赖检测仅验证运行环境是否存在,不验证具体版本。`java -version` 能正常输出即判定为就绪,但不检查是否满足 Java 8+ 要求。检测操作使用 `execSync` 同步执行,需设置 5 秒超时防止卡死。检测结果需缓存,避免每次刷新面板都执行命令行检测(建议在 `collectAdapterStatus()` 中缓存,配置变更时重新检测)。 ### collectAdapterStatus() — 汇总适配器状态 遍历 `ADAPTER_METADATA`,调用上述检测方法,组装 `AdapterConfigStatus[]` 数组。 ```typescript function collectAdapterStatus(): AdapterConfigStatus[] { const statuses: AdapterConfigStatus[] = []; for (const [id, meta] of Object.entries(ADAPTER_METADATA)) { const configMode = detectConfigMode(id); const enabled = isAdapterEnabled(id); let dependencyStatus: DependencyStatus = 'none'; if (meta.hasExternalDependency) { if (id === 'pmd') { dependencyStatus = checkJavaReady() ? 'ready' : 'missing'; } else if (id === 'sql-lint') { dependencyStatus = checkPythonReady() ? 'ready' : 'missing'; } } const configured = configMode !== 'builtin' || !meta.hasExternalDependency; statuses.push({ id, name: meta.name, enabled, configMode, dependencyStatus, dependencyLabel: meta.dependencyLabel, configured, guideText: meta.guideText, projectConfigFileName: meta.projectConfigFileName, settingsTarget: meta.settingsTarget, }); } return statuses; } ``` --- ## 07 侧边栏面板实现 ### 面板位置 新增区域位于侧边栏中**"审核引擎"区域之后、"AI 模型配置"区域之前**。视线流程为:快速开始引导 → 引擎状态总览 → **适配器配置引导(本节)** → AI 模型配置 → 规则文件管理。 ### setupView.ts — pushConfig() 变更 在 `pushConfig()` 方法中追加 `adapterStatus` 字段,将采集的适配器状态推送到前端: ```typescript private pushConfig() { // ... 现有配置推送逻辑保持不变 ... // 追加适配器状态 const adapterStatus = this.collectAdapterStatus(); this.view?.webview.postMessage({ type: 'initConfig', // ... 现有字段 ... adapterStatus, }); } ``` ### setupView.ts — onDidReceiveMessage 新增消息处理 在 `resolveWebviewView` 的 `onDidReceiveMessage` 回调中,新增三个 case: ```typescript this.view.webview.onDidReceiveMessage(async (message) => { switch (message.type) { // ... 现有 case 保持不变 ... case 'openAdapterConfig': { await this.handleAdapterConfig(message.adapterId); break; } case 'openSettings': { await vscode.commands.executeCommand( 'workbench.action.openSettings', message.settingsTarget ); break; } case 'toggleAdapter': { await setAdapterEnabled(message.adapterId, message.enabled); // 配置变更后 pushConfig 会由 onDidChangeConfiguration 触发 break; } } }); ``` ### handleAdapterConfig() — 配置文件创建/打开 ```typescript private async handleAdapterConfig(adapterId: string) { const meta = ADAPTER_METADATA[adapterId]; if (!meta) return; const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { vscode.window.showWarningMessage('请先打开一个工作区文件夹'); return; } const rootPath = workspaceFolders[0].uri.fsPath; const filePath = path.join(rootPath, meta.projectConfigFileName); if (!fs.existsSync(filePath)) { // 文件不存在 → 创建默认模板 fs.writeFileSync(filePath, meta.configFileTemplate, 'utf-8'); vscode.window.showInformationMessage(`配置文件已创建: ${meta.projectConfigFileName}`); } // 打开文件 const doc = await vscode.workspace.openTextDocument(filePath); await vscode.window.showTextDocument(doc); } ``` ### resolveWebviewView — 配置变更监听 在 `resolveWebviewView` 方法中注册 `onDidChangeConfiguration` 监听器,当 `vscode-code-reviewer.linter` 或 `vscode-code-reviewer.linters` 或 `vscode-code-reviewer.pmd` 或 `vscode-code-reviewer.sql-lint` 命名空间下的配置变更时,触发 `pushConfig()` 刷新: ```typescript resolveWebviewView(view: vscode.WebviewView) { this.view = view; // ... 现有初始化逻辑 ... // 新增:配置变更监听 const configChangeDisposable = vscode.workspace.onDidChangeConfiguration((e) => { if ( e.affectsConfiguration('vscode-code-reviewer.linter') || e.affectsConfiguration('vscode-code-reviewer.linters') || e.affectsConfiguration('vscode-code-reviewer.pmd') || e.affectsConfiguration('vscode-code-reviewer.sql-lint') ) { this.pushConfig(); } }); // 随 webview 销毁自动清理 view.onDidDispose(() => { configChangeDisposable.dispose(); }); } ``` ### setupView.ts — getHtml() 模板变更 在 `getHtml()` 方法返回的 HTML 模板中,在"审核引擎"区域和"AI 模型配置"区域之间插入以下 HTML: ```html

静态分析适配器

``` 同时在 `