Files
2026Technology-Competition/docs/superpowers/specs/implementation-steps/08-phase3-orchestrator.md
T

3.6 KiB

Step 08 — Phase 3: 编排器 Orchestrator

依赖: Step 02~07(所有适配器)
参考设计: §3.6, §4

目标

实现单文件保存触发、多适配器调度、500ms debounce、结果聚合的编排器。

新建文件

# 文件 说明
1 src/orchestrator/orchestrator.ts Orchestrator

src/orchestrator/orchestrator.ts

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:

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 通过