# Step 05 — Phase 2.4: sql-lint 适配器 **依赖**: Step 02 **参考设计**: §3.3 ## 目标 实现 SQL / PL/SQL 的适配器,通过 CLI 子进程调用 sqlfluff。 ## 新建文件 | # | 文件 | 说明 | |---|------|------| | 1 | `src/adapters/sql-lint.ts` | `SqlLintAdapter` | --- ## `src/adapters/sql-lint.ts` ```typescript import * as vscode from 'vscode'; import * as child_process from 'child_process'; import { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types'; import { getLinterConfig } from '../config'; const DIALECT_MAP: Record = { sql: 'ansi', plsql: 'postgres', }; export class SqlLintAdapter implements LinterAdapter { id = 'sql-lint'; supportedLanguages = ['sql', 'plsql']; async check(document: vscode.TextDocument, workingDir: string): Promise { try { const code = document.getText(); const languageId = document.languageId; const dialect = DIALECT_MAP[languageId] ?? 'ansi'; const config = getLinterConfig(); const args = ['lint', '--format', 'json', '--dialect', dialect, '-']; if (config.sqlLintConfigFile) { args.push('--config', config.sqlLintConfigFile); } const result = await this.execSqlfluff(code, args, workingDir); const output = JSON.parse(result); const diagnostics: LinterDiagnostic[] = []; for (const violation of output) { const line = Math.max(0, (violation.line_no ?? violation.line_pos ?? 1) - 1); const col = Math.max(0, (violation.line_pos ?? 1) - 1); const range = new vscode.Range(line, col, line, col + 1); diagnostics.push({ severity: 'warning', ruleId: `sql-lint:${violation.code ?? violation.rule ?? 'unknown'}`, message: violation.description ?? violation.message ?? '', range, }); } return { diagnostics, status: 'ok' }; } catch (err) { const message = err instanceof Error ? err.message : String(err); if (message.includes('ENOENT') || message.includes('not found')) { return { diagnostics: [], status: 'tool-unavailable', errorMessage: 'sqlfluff 未安装,请执行 pip install sqlfluff' }; } return { diagnostics: [], status: 'execution-failed', errorMessage: message }; } } private execSqlfluff(code: string, args: string[], cwd: string): Promise { return new Promise((resolve, reject) => { const proc = child_process.spawn('sqlfluff', args, { cwd }); let stdout = ''; let stderr = ''; proc.stdout.on('data', (data: Buffer) => { stdout += data.toString(); }); proc.stderr.on('data', (data: Buffer) => { stderr += data.toString(); }); proc.on('close', (code) => { if (code === 0 || stdout.length > 0) { resolve(stdout); } else { reject(new Error(stderr || `sqlfluff exited with code ${code}`)); } }); proc.on('error', reject); proc.stdin.write(code); proc.stdin.end(); }); } isAvailable(): boolean { try { child_process.execSync('sqlfluff --version', { stdio: 'ignore' }); return true; } catch { return false; } } } ``` --- ## 关键逻辑 - `check()`: spawn `sqlfluff lint --format json --dialect -`,stdin 传入代码 - 方言映射:`sql → ansi`, `plsql → postgres` - ruleId 加前缀 `sql-lint:` - `isAvailable()`: `sqlfluff --version` 检测 - 通过 stdin 传代码,无需真实文件(支持虚拟文档) --- ## 验收 - [ ] 文件创建完成 - [ ] `npm run compile` 通过 - [ ] `npm run lint` 通过