# 静态分析适配器优化设计书(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
```
同时在 `