UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

92 lines (91 loc) • 3.37 kB
import * as fs from "node:fs"; import * as path from "node:path"; export class AstGrepRuleManager { ruleDir; log; ruleDescriptions = null; constructor(ruleDir, log) { this.ruleDir = ruleDir; this.log = log; } loadRuleDescriptions() { if (this.ruleDescriptions !== null) return this.ruleDescriptions; const descriptions = new Map(); const possiblePaths = [ path.join(this.ruleDir, "ast-grep-rules", "rules"), path.join(this.ruleDir, "rules"), this.ruleDir, ]; const rulesPath = possiblePaths.find((p) => fs.existsSync(p)); if (!rulesPath) { this.log(`Rule descriptions: no rules directory found in ${possiblePaths.join(", ")}`); this.ruleDescriptions = descriptions; return descriptions; } try { const files = fs.readdirSync(rulesPath).filter((f) => f.endsWith(".yml")); this.log(`Loaded ${files.length} rule descriptions from ${rulesPath}`); for (const file of files) { const filePath = path.join(rulesPath, file); const content = fs.readFileSync(filePath, "utf-8"); const rule = this.parseRuleYaml(content); if (rule) { descriptions.set(rule.id, rule); } } } catch (err) { this.log(`Failed to load rule descriptions: ${err.message}`); } this.ruleDescriptions = descriptions; return descriptions; } parseRuleYaml(content) { const result = {}; const idMatch = content.match(/^id:\s*(.+)$/m); if (idMatch) result.id = idMatch[1].trim(); const msgMatch = content.match(/^message:\s*"([^"]+)"/m) || content.match(/^message:\s*'([^']+)'/m) || content.match(/^message:\s*(.+)$/m); if (msgMatch) result.message = (msgMatch[3] || msgMatch[2] || msgMatch[1]).trim(); const noteMatch = content.match(/^note:\s*\|([\s\S]*?)(?=^\w|\n\n|\nrule:)/m); if (noteMatch) { result.note = noteMatch[1] .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line.length > 0) .join(" "); } const sevMatch = content.match(/^severity:\s*(.+)$/m); if (sevMatch) result.severity = this.mapSeverity(sevMatch[1].trim()); const gradeMatch = content.match(/Grade\s+(\d+\.\d+)/i); if (gradeMatch) result.grade = parseFloat(gradeMatch[1]); const fixMatch = content.match(/^fix:\s*\|?([\s\S]*?)(?=^\w|^rule:|Z)/m); if (fixMatch) { result.fix = fixMatch[1] .split(/\r?\n/) .map((line) => line.replace(/^\s*\|?\s*/, "")) .filter((line) => line.length > 0) .join("\n"); } if (result.id && result.message) { return result; } return null; } mapSeverity(severity) { const lower = severity.toLowerCase(); if (lower === "error") return "error"; if (lower === "warning") return "warning"; if (lower === "info") return "info"; return "hint"; } }