feat: implement core code review extension

This commit is contained in:
Developer
2026-07-14 21:18:19 +08:00
parent cafe67db6d
commit 1144c9b5db
39 changed files with 7541 additions and 132 deletions
+114
View File
@@ -0,0 +1,114 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { execSync, spawn } from 'child_process';
import type { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types';
import { getPMDRulesetPath } from '../config';
export class PmdAdapter implements LinterAdapter {
id = 'pmd';
supportedLanguages = ['java'];
private getPmdLibClasspath(): string {
const extRoot = this.getExtensionRoot();
const pmdLib = path.join(extRoot, 'jars', 'pmd', 'lib');
return path.join(pmdLib, '*');
}
private getPmdRunnerClasspath(): string {
const extRoot = this.getExtensionRoot();
return path.join(extRoot, 'jars', 'pmd');
}
private getExtensionRoot(): string {
try {
const extPath = vscode.extensions.getExtension?.('vscode-code-reviewer')?.extensionPath;
if (extPath) { return extPath; }
} catch { /* extension not available */ }
return path.join(__dirname, '..', '..');
}
async check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
try {
const ruleset = getPMDRulesetPath()
|| path.join(this.getPmdRunnerClasspath(), 'pmd-java-ruleset.xml');
const classpath = `${this.getPmdLibClasspath()};${this.getPmdRunnerClasspath()}`;
const isVirtual = document.uri.scheme === 'untitled';
const fileArg = isVirtual ? '-' : document.uri.fsPath;
const javaArgs = ['-cp', classpath, 'PmdRunner', fileArg, ruleset];
const result = await this.execPmd(javaArgs, isVirtual ? document.getText() : null, workingDir);
const diagnostics = this.parsePmdOutput(result);
return { diagnostics, status: 'ok' };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes('ENOENT') || message.includes('java')) {
return { diagnostics: [], status: 'tool-unavailable', errorMessage: 'Java 11+ 未安装或不在 PATH 中' };
}
return { diagnostics: [], status: 'execution-failed', errorMessage: message };
}
}
private execPmd(args: string[], stdinInput: string | null, cwd: string): Promise<string> {
return new Promise((resolve, reject) => {
const proc = spawn('java', 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 || code === 4 || stdout.length > 0) {
resolve(stdout);
} else {
reject(new Error(stderr || `PMD exited with code ${code}`));
}
});
proc.on('error', reject);
if (stdinInput !== null) {
proc.stdin.write(stdinInput);
proc.stdin.end();
}
});
}
private parsePmdOutput(output: string): LinterDiagnostic[] {
if (!output.trim()) { return []; }
try {
const data = JSON.parse(output);
const diagnostics: LinterDiagnostic[] = [];
for (const file of data.files ?? []) {
for (const violation of file.violations ?? []) {
const line = Math.max(0, (violation.beginline ?? 1) - 1);
const col = Math.max(0, (violation.begincolumn ?? 1) - 1);
const endCol = Math.max(col, (violation.endcolumn ?? col + 1) - 1);
const range = new vscode.Range(line, col, line, endCol);
diagnostics.push({
severity: this.mapPriority(violation.priority),
ruleId: `pmd:${violation.rule}`,
message: violation.description ?? '',
range,
});
}
}
return diagnostics;
} catch {
return [];
}
}
private mapPriority(priority: number): 'error' | 'warning' | 'info' {
if (priority <= 2) { return 'error'; }
if (priority === 3) { return 'warning'; }
return 'info';
}
isAvailable(): boolean {
try {
execSync('java -version 2>&1', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
}