140 lines
3.6 KiB
Markdown
140 lines
3.6 KiB
Markdown
# Step 08 — Phase 3: 编排器 Orchestrator
|
|
|
|
**依赖**: Step 02~07(所有适配器)
|
|
**参考设计**: §3.6, §4
|
|
|
|
## 目标
|
|
|
|
实现单文件保存触发、多适配器调度、500ms debounce、结果聚合的编排器。
|
|
|
|
## 新建文件
|
|
|
|
| # | 文件 | 说明 |
|
|
|---|------|------|
|
|
| 1 | `src/orchestrator/orchestrator.ts` | `Orchestrator` 类 |
|
|
|
|
---
|
|
|
|
## `src/orchestrator/orchestrator.ts`
|
|
|
|
```typescript
|
|
import * as vscode from 'vscode';
|
|
import { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types';
|
|
import { getLinterConfig } from '../config';
|
|
import { ESLintAdapter } from '../adapters/eslint';
|
|
import { PmdAdapter } from '../adapters/pmd';
|
|
import { StylelintAdapter } from '../adapters/stylelint';
|
|
import { SqlLintAdapter } from '../adapters/sql-lint';
|
|
import { JspAdapter } from '../adapters/jsp';
|
|
|
|
export interface StaticAnalysisResult {
|
|
diagnostics: LinterDiagnostic[];
|
|
errors: string[];
|
|
adapterIds: string[];
|
|
duration: number;
|
|
}
|
|
|
|
export class Orchestrator {
|
|
private adapters: LinterAdapter[];
|
|
|
|
constructor() {
|
|
this.adapters = [
|
|
new ESLintAdapter(),
|
|
new PmdAdapter(),
|
|
new StylelintAdapter(),
|
|
new SqlLintAdapter(),
|
|
new JspAdapter(),
|
|
];
|
|
}
|
|
|
|
getAdapterMap(): Map<string, LinterAdapter> {
|
|
const map = new Map<string, LinterAdapter>();
|
|
for (const adapter of this.adapters) {
|
|
for (const lang of adapter.supportedLanguages) {
|
|
map.set(lang, adapter);
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
async runStaticAnalysis(
|
|
document: vscode.TextDocument,
|
|
workingDir: string
|
|
): Promise<StaticAnalysisResult> {
|
|
const startTime = Date.now();
|
|
const config = getLinterConfig();
|
|
const languageId = document.languageId;
|
|
|
|
const selectedLinter = config.languageMap[languageId];
|
|
if (!selectedLinter || selectedLinter === '') {
|
|
return { diagnostics: [], errors: [], adapterIds: [], duration: 0 };
|
|
}
|
|
|
|
const adapter = this.adapters.find(a => a.id === selectedLinter);
|
|
if (!adapter) {
|
|
return {
|
|
diagnostics: [],
|
|
errors: [`未找到适配器: ${selectedLinter}`],
|
|
adapterIds: [],
|
|
duration: Date.now() - startTime,
|
|
};
|
|
}
|
|
|
|
const result = await adapter.check(document, workingDir);
|
|
const errors: string[] = [];
|
|
if (result.status !== 'ok') {
|
|
errors.push(`[${adapter.id}] ${result.errorMessage ?? result.status}`);
|
|
}
|
|
|
|
return {
|
|
diagnostics: result.diagnostics,
|
|
errors,
|
|
adapterIds: [adapter.id],
|
|
duration: Date.now() - startTime,
|
|
};
|
|
}
|
|
|
|
getAdaptersByIds(ids: string[]): LinterAdapter[] {
|
|
return ids.map(id => this.adapters.find(a => a.id === id)).filter(Boolean) as LinterAdapter[];
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## debounce 工具函数
|
|
|
|
在 `src/orchestrator/orchestrator.ts` 中或另外创建 `src/utils/debounce.ts`:
|
|
|
|
```typescript
|
|
export function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): (...args: Parameters<T>) => void {
|
|
let timer: NodeJS.Timeout;
|
|
return (...args: Parameters<T>) => {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn(...args), ms);
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 关键逻辑
|
|
|
|
- `getAdapterMap()`: 建立 language → adapter 的映射表
|
|
- `runStaticAnalysis()`:
|
|
1. 获取文档 languageId
|
|
2. 查 `linters.<language>` 配置 → 确定使用的适配器
|
|
3. 调用 `adapter.check(document, workingDir)`
|
|
4. 收集结果,区分 status
|
|
5. 返回聚合 `StaticAnalysisResult`
|
|
- 硬编码注册 5 个适配器(工厂函数方式)
|
|
- 禁用某语言的配置项为空字符串时不执行检查
|
|
|
|
---
|
|
|
|
## 验收
|
|
|
|
- [ ] 文件创建完成
|
|
- [ ] `npm run compile` 通过
|
|
- [ ] `npm run lint` 通过
|