Files
supabase-postgres-best-prac…/packages/postgres-best-practices-build/src/parser.ts
Pedro Rodrigues f323d3b601 Add Biome formatter/linter and restore CI workflow (#6)
- Install Biome as the project formatter and linter
- Configure Biome with recommended settings
- Add format, lint, and check scripts to package.json
- Restore CI workflow from git history (commit 0a543e1)
- Extend CI with new Biome job for format and lint checks
- Apply Biome formatting to all TypeScript files
- Fix linting issues (use node: protocol, template literals, forEach pattern)

CI now runs on:
- All pushes to main branch
- All pull requests

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-22 08:28:49 +00:00

276 lines
6.8 KiB
TypeScript

import { readFileSync } from "node:fs";
import { basename } from "node:path";
import { IMPACT_LEVELS, SECTION_MAP } from "./config.js";
import type { CodeExample, ImpactLevel, ParseResult, Rule } from "./types.js";
/**
* Parse YAML-style frontmatter from markdown content
*/
function parseFrontmatter(content: string): {
frontmatter: Record<string, string>;
body: string;
} {
const frontmatter: Record<string, string> = {};
if (!content.startsWith("---")) {
return { frontmatter, body: content };
}
const endIndex = content.indexOf("---", 3);
if (endIndex === -1) {
return { frontmatter, body: content };
}
const frontmatterContent = content.slice(3, endIndex).trim();
const body = content.slice(endIndex + 3).trim();
for (const line of frontmatterContent.split("\n")) {
const colonIndex = line.indexOf(":");
if (colonIndex === -1) continue;
const key = line.slice(0, colonIndex).trim();
let value = line.slice(colonIndex + 1).trim();
// Strip quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
frontmatter[key] = value;
}
return { frontmatter, body };
}
/**
* Extract section number from filename prefix
*/
function getSectionFromFilename(filename: string): number | null {
const base = basename(filename, ".md");
const prefix = base.split("-")[0];
return SECTION_MAP[prefix] ?? null;
}
/**
* Extract code examples from markdown body
*/
function extractExamples(body: string): CodeExample[] {
const examples: CodeExample[] = [];
const lines = body.split("\n");
let currentLabel = "";
let currentDescription = "";
let inCodeBlock = false;
let codeBlockLang = "";
let codeBlockContent: string[] = [];
let additionalText: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Check for example label: **Label:** or **Label (description):**
const labelMatch = line.match(
/^\*\*([^*]+?)(?:\s*\(([^)]+)\))?\s*:\*\*\s*$/,
);
if (labelMatch && !inCodeBlock) {
// Save previous example if exists
if (currentLabel && codeBlockContent.length > 0) {
examples.push({
label: currentLabel,
description: currentDescription || undefined,
code: codeBlockContent.join("\n"),
language: codeBlockLang || undefined,
additionalText:
additionalText.length > 0
? additionalText.join("\n").trim()
: undefined,
});
}
currentLabel = labelMatch[1].trim();
currentDescription = labelMatch[2]?.trim() || "";
codeBlockContent = [];
codeBlockLang = "";
additionalText = [];
continue;
}
// Check for code block start
if (line.startsWith("```") && !inCodeBlock) {
inCodeBlock = true;
codeBlockLang = line.slice(3).trim();
continue;
}
// Check for code block end
if (line.startsWith("```") && inCodeBlock) {
inCodeBlock = false;
continue;
}
// Collect code block content
if (inCodeBlock) {
codeBlockContent.push(line);
continue;
}
// Collect additional text after code block (before next label)
if (currentLabel && codeBlockContent.length > 0 && line.trim()) {
// Stop collecting if we hit a heading or reference
if (line.startsWith("#") || line.startsWith("Reference")) {
continue;
}
additionalText.push(line);
}
}
// Save last example
if (currentLabel && codeBlockContent.length > 0) {
examples.push({
label: currentLabel,
description: currentDescription || undefined,
code: codeBlockContent.join("\n"),
language: codeBlockLang || undefined,
additionalText:
additionalText.length > 0
? additionalText.join("\n").trim()
: undefined,
});
}
return examples;
}
/**
* Extract title from first ## heading
*/
function extractTitle(body: string): string | null {
const match = body.match(/^##\s+(.+)$/m);
return match ? match[1].trim() : null;
}
/**
* Extract explanation (content between title and first example)
*/
function extractExplanation(body: string): string {
const lines = body.split("\n");
const explanationLines: string[] = [];
let foundTitle = false;
for (const line of lines) {
if (line.startsWith("## ")) {
foundTitle = true;
continue;
}
if (!foundTitle) continue;
// Stop at first example label or code block
if (line.match(/^\*\*[^*]+:\*\*/) || line.startsWith("```")) {
break;
}
explanationLines.push(line);
}
return explanationLines.join("\n").trim();
}
/**
* Extract references from body
*/
function extractReferences(body: string): string[] {
const references: string[] = [];
const lines = body.split("\n");
for (const line of lines) {
// Match "Reference: [text](url)" or "- [text](url)" after "References:"
const refMatch = line.match(/Reference:\s*\[([^\]]+)\]\(([^)]+)\)/);
if (refMatch) {
references.push(refMatch[2]);
continue;
}
// Match list items under References section
const listMatch = line.match(/^-\s*\[([^\]]+)\]\(([^)]+)\)/);
if (listMatch) {
references.push(listMatch[2]);
}
}
return references;
}
/**
* Parse a rule file and return structured data
*/
export function parseRuleFile(filePath: string): ParseResult {
const errors: string[] = [];
const warnings: string[] = [];
try {
const content = readFileSync(filePath, "utf-8");
const { frontmatter, body } = parseFrontmatter(content);
// Extract section from filename
const section = getSectionFromFilename(filePath);
if (section === null) {
errors.push(
`Could not determine section from filename: ${basename(filePath)}`,
);
return { success: false, errors, warnings };
}
// Get title from frontmatter or body
const title = frontmatter.title || extractTitle(body);
if (!title) {
errors.push("Missing title in frontmatter or body");
return { success: false, errors, warnings };
}
// Get impact level
const impact = frontmatter.impact as ImpactLevel;
if (!impact || !IMPACT_LEVELS.includes(impact)) {
errors.push(
`Invalid or missing impact level: ${impact}. Must be one of: ${IMPACT_LEVELS.join(", ")}`,
);
return { success: false, errors, warnings };
}
// Extract other fields
const explanation = extractExplanation(body);
const examples = extractExamples(body);
const tags = frontmatter.tags?.split(",").map((t) => t.trim()) || [];
// Validation warnings
if (!explanation || explanation.length < 20) {
warnings.push("Explanation is very short or missing");
}
if (examples.length === 0) {
warnings.push("No code examples found");
}
const rule: Rule = {
id: "", // Will be assigned during build
title,
section,
impact,
impactDescription: frontmatter.impactDescription,
explanation,
examples,
references: extractReferences(body),
tags: tags.length > 0 ? tags : undefined,
};
return { success: true, rule, errors, warnings };
} catch (error) {
errors.push(`Failed to parse file: ${error}`);
return { success: false, errors, warnings };
}
}