UNPKG

@entro314labs/starlight-document-converter

Version:

A comprehensive document converter for Astro Starlight that transforms various document formats into Starlight-compatible Markdown with proper frontmatter

304 lines (303 loc) 9.05 kB
// src/plugins/built-in/quality-validator.ts var contentQualityValidator = { metadata: { name: "content-quality-validator", version: "1.0.0", description: "Validates content quality and provides improvement suggestions", author: "Starlight Document Converter" }, validate: (content, metadata, _context) => { const issues = []; let score = 100; const metadataScore = validateMetadata(metadata, issues); const structureScore = validateContentStructure(content, issues); const contentScore = validateContentQuality(content, issues); const accessibilityScore = validateAccessibility(content, issues); score = Math.round( metadataScore * 0.25 + structureScore * 0.3 + contentScore * 0.35 + accessibilityScore * 0.1 ); const level = score >= 80 ? "high" : score >= 60 ? "medium" : "low"; const suggestions = generateSuggestions(issues, metadata, content); return { score, level, issues, suggestions }; } }; function validateMetadata(metadata, issues) { let score = 100; if (!metadata.title) { issues.push({ type: "error", message: "Missing title", severity: 9 }); score -= 30; } else if (metadata.title.length < 5) { issues.push({ type: "warning", message: "Title is very short (less than 5 characters)", severity: 6 }); score -= 15; } else if (metadata.title.length > 100) { issues.push({ type: "warning", message: "Title is very long (over 100 characters)", severity: 4 }); score -= 10; } if (!metadata.description) { issues.push({ type: "warning", message: "Missing description", severity: 7 }); score -= 20; } else if (metadata.description.length < 20) { issues.push({ type: "warning", message: "Description is very short (less than 20 characters)", severity: 5 }); score -= 10; } else if (metadata.description.length > 300) { issues.push({ type: "info", message: "Description is quite long (over 300 characters)", severity: 2 }); score -= 5; } if (!metadata.category) { issues.push({ type: "info", message: "No category specified", severity: 3 }); score -= 5; } if (!metadata.tags || metadata.tags.length === 0) { issues.push({ type: "info", message: "No tags specified", severity: 2 }); score -= 5; } else if (metadata.tags.length > 10) { issues.push({ type: "warning", message: "Too many tags (over 10)", severity: 3 }); score -= 5; } return Math.max(0, score); } function validateContentStructure(content, issues) { let score = 100; const headings = content.match(/^#{1,6}\s+.+$/gm) || []; if (headings.length === 0) { issues.push({ type: "warning", message: "No headings found - content may lack structure", severity: 6 }); score -= 20; } else if (headings.length > 20) { issues.push({ type: "info", message: "Many headings found - consider consolidating content", severity: 2 }); score -= 5; } const headingLevels = headings.map((h) => h.match(/^#+/)?.[0]?.length || 0); let prevLevel = 0; let hierarchyIssues = 0; headingLevels.forEach((level, index) => { if (index > 0 && level > prevLevel + 1) { hierarchyIssues++; } prevLevel = level; }); if (hierarchyIssues > 0) { issues.push({ type: "warning", message: "Heading hierarchy has gaps (e.g., H1 followed by H3)", severity: 4 }); score -= 10; } const wordCount = content.split(/\s+/).length; if (wordCount < 50) { issues.push({ type: "warning", message: "Content is very short (less than 50 words)", severity: 5 }); score -= 15; } else if (wordCount > 5e3) { issues.push({ type: "info", message: "Content is very long (over 5000 words) - consider splitting", severity: 2 }); score -= 5; } const codeBlocks = content.match(/```[\s\S]*?```/g) || []; const inlineCode = content.match(/`[^`]+`/g) || []; if (codeBlocks.length > 0 || inlineCode.length > 0) { const unspecifiedCodeBlocks = content.match(/```\n/g) || []; if (unspecifiedCodeBlocks.length > 0) { issues.push({ type: "info", message: "Some code blocks lack language specification", severity: 1 }); score -= 2; } } return Math.max(0, score); } function validateContentQuality(content, issues) { let score = 100; const placeholders = [ "lorem ipsum", "todo", "tbd", "fixme", "xxx", "placeholder", "coming soon", "under construction" ]; const lowerContent = content.toLowerCase(); placeholders.forEach((placeholder) => { if (lowerContent.includes(placeholder)) { issues.push({ type: "warning", message: `Found placeholder text: "${placeholder}"`, severity: 7 }); score -= 15; } }); const links = content.match(/\[([^\]]+)\]\(([^)]+)\)/g) || []; links.forEach((link) => { const url = link.match(/\]\(([^)]+)\)/)?.[1]; if (url?.includes("example.com") || url?.includes("localhost")) { issues.push({ type: "warning", message: "Found example or localhost link that may need updating", severity: 4 }); score -= 5; } }); const internalLinks = links.filter( (link) => link.includes("](./") || link.includes("](../") || link.includes("](/") ); if (internalLinks.length > 0) { issues.push({ type: "info", message: "Internal links found - verify they point to existing files", severity: 3 }); } const sentences = content.split(/[.!?]+/).filter((s) => s.trim().length > 10); const duplicateSentences = sentences.filter( (sentence, index) => sentences.indexOf(sentence) !== index ); if (duplicateSentences.length > 0) { issues.push({ type: "info", message: "Found potentially duplicate sentences", severity: 2 }); score -= 5; } return Math.max(0, score); } function validateAccessibility(content, issues) { let score = 100; const images = content.match(/!\[([^\]]*)\]\([^)]+\)/g) || []; const imagesWithoutAlt = images.filter((img) => { const altText = img.match(/!\[([^\]]*)\]/)?.[1]; return !altText || altText.trim().length === 0; }); if (imagesWithoutAlt.length > 0) { issues.push({ type: "warning", message: `${imagesWithoutAlt.length} image(s) without alt text`, severity: 6 }); score -= 20; } const tables = content.match(/\|[^|\n]*\|/g) || []; if (tables.length > 0) { const hasTableHeaders = content.includes("|--") || content.includes("| --"); if (!hasTableHeaders) { issues.push({ type: "warning", message: "Tables found without proper headers", severity: 4 }); score -= 10; } } const colorWords = ["red", "green", "blue", "yellow", "orange", "purple"]; const colorOnlyReferences = colorWords.some((color) => { const pattern = new RegExp(`\\b${color}\\s+(indicates?|means?|shows?)`, "i"); return pattern.test(content); }); if (colorOnlyReferences) { issues.push({ type: "info", message: "Content may rely on color alone for meaning", severity: 3 }); score -= 5; } return Math.max(0, score); } function generateSuggestions(issues, metadata, content) { const suggestions = []; const errorCount = issues.filter((i) => i.type === "error").length; const warningCount = issues.filter((i) => i.type === "warning").length; if (errorCount > 0) { suggestions.push("Fix critical errors first, especially missing titles or descriptions"); } if (warningCount > 0) { suggestions.push("Address warnings to improve content quality and user experience"); } if (!metadata.title || metadata.title && metadata.title.length < 10) { suggestions.push("Consider a more descriptive title that clearly explains the content"); } if (!metadata.description || metadata.description && metadata.description.length < 50) { suggestions.push("Add a comprehensive description that summarizes the key points"); } const headingCount = (content.match(/^#+/gm) || []).length; if (headingCount === 0) { suggestions.push("Add headings to improve content structure and readability"); } const wordCount = content.split(/\s+/).length; if (wordCount < 100) { suggestions.push("Consider expanding the content with more details and examples"); } if (!metadata.tags || metadata.tags.length === 0) { suggestions.push("Add relevant tags to improve discoverability"); } const codeBlockCount = (content.match(/```/g) || []).length / 2; if (codeBlockCount > 0) { suggestions.push("Ensure all code blocks specify their programming language"); } return suggestions; } export { contentQualityValidator }; //# sourceMappingURL=chunk-X2OVO7PR.js.map