Files

3.6 KiB
Raw Permalink Blame History

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

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<string, string> = {
  sql: 'ansi',
  plsql: 'postgres',
};

export class SqlLintAdapter implements LinterAdapter {
  id = 'sql-lint';
  supportedLanguages = ['sql', 'plsql'];

  async check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
    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<string> {
    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 <dialect> -stdin 传入代码
  • 方言映射:sql → ansi, plsql → postgres
  • ruleId 加前缀 sql-lint:
  • isAvailable(): sqlfluff --version 检测
  • 通过 stdin 传代码,无需真实文件(支持虚拟文档)

验收

  • 文件创建完成
  • npm run compile 通过
  • npm run lint 通过