UNPKG

doctool

Version:

AI-powered documentation validation and management system

258 lines 9.52 kB
import * as fs from 'fs'; import * as path from 'path'; /** * Applies targeted fixes to documentation issues */ export function applyDocumentationFixes(analysis, options = {}) { const { dryRun = false, severityThreshold = 'medium', autoApprove = true } = options; // Read current content let content = ''; try { content = fs.readFileSync(analysis.filePath, 'utf8'); } catch (error) { content = ''; } const results = []; let modifiedContent = content; // Filter issues by severity threshold const severityLevels = { low: 0, medium: 1, high: 2 }; const threshold = severityLevels[severityThreshold]; const issuesToFix = analysis.issues.filter(issue => severityLevels[issue.severity] >= threshold); // Apply fixes in order of severity (high -> medium -> low) const sortedIssues = issuesToFix.sort((a, b) => severityLevels[b.severity] - severityLevels[a.severity]); for (const issue of sortedIssues) { const result = applyIssuseFix(issue, modifiedContent, { dryRun, autoApprove }); results.push(result); if (result.applied && result.changes) { modifiedContent = result.changes; } } // Write back the modified content if (!dryRun && modifiedContent !== content) { try { fs.writeFileSync(analysis.filePath, modifiedContent, 'utf8'); } catch (error) { console.error(`Failed to write fixes to ${analysis.filePath}:`, error); } } const fixesApplied = results.filter(r => r.applied).length; const fixesSkipped = results.filter(r => !r.applied).length; return { filePath: analysis.filePath, totalIssues: analysis.issues.length, fixesApplied, fixesSkipped, results }; } /** * Applies a single issue fix */ function applyIssuseFix(issue, content, options) { const { dryRun = false, autoApprove = true } = options; if (!autoApprove) { // In interactive mode, would prompt user here // For now, we'll auto-approve all fixes } try { let modifiedContent = content; switch (issue.suggestedFix.action) { case 'add_files': modifiedContent = addFileToDocumentation(content, issue); break; case 'add_section': modifiedContent = addMissingSection(content, issue); break; case 'update_content': modifiedContent = updateFileDescription(content, issue); break; case 'remove_placeholder': modifiedContent = removePlaceholderContent(content, issue); break; default: return { issue, applied: false, reason: `Unknown fix action: ${issue.suggestedFix.action}` }; } if (modifiedContent === content) { return { issue, applied: false, reason: 'No changes would be made' }; } return { issue, applied: true, changes: modifiedContent }; } catch (error) { return { issue, applied: false, reason: `Error applying fix: ${error}` }; } } /** * Adds a missing file to the documentation */ function addFileToDocumentation(content, issue) { const fileName = issue.suggestedFix.target; const description = issue.suggestedFix.content; // Find the Files section const lines = content.split('\n'); let filesSectionIndex = -1; let insertIndex = -1; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Look for "### Files" section if (line.trim() === '### Files') { filesSectionIndex = i; continue; } // If we're in the files section, find where to insert if (filesSectionIndex !== -1 && insertIndex === -1) { // Look for the end of the files list if (line.trim() === '' || line.startsWith('### ') || line.startsWith('## ')) { insertIndex = i; break; } // Insert alphabetically if (line.startsWith('- `') && line > `- \`${fileName}\``) { insertIndex = i; break; } } } // If we found the files section but no insert point, append to the end of the section if (filesSectionIndex !== -1 && insertIndex === -1) { insertIndex = lines.length; } // If we couldn't find a files section, find the Contents section and add one if (filesSectionIndex === -1) { const contentsIndex = lines.findIndex(line => line.trim() === '## Contents'); if (contentsIndex !== -1) { const filesToAdd = [ '', '### Files', `- \`${fileName}\` - ${description}`, '' ]; lines.splice(contentsIndex + 1, 0, ...filesToAdd); return lines.join('\n'); } } // Add the file to the list if (insertIndex !== -1) { lines.splice(insertIndex, 0, `- \`${fileName}\` - ${description}`); } return lines.join('\n'); } /** * Adds a missing section to the documentation */ function addMissingSection(content, issue) { const sectionContent = issue.suggestedFix.content; const sectionName = issue.suggestedFix.target; const lines = content.split('\n'); // Find the best place to insert the section let insertIndex = -1; // Standard section order const sectionOrder = ['Overview', 'Contents', 'Purpose', 'Key Components', 'Dependencies', 'Notes']; const sectionIndex = sectionOrder.indexOf(sectionName); if (sectionIndex !== -1) { // Try to insert in the correct order for (let i = sectionIndex + 1; i < sectionOrder.length; i++) { const nextSection = sectionOrder[i]; const nextSectionIndex = lines.findIndex(line => line.trim() === `## ${nextSection}`); if (nextSectionIndex !== -1) { insertIndex = nextSectionIndex; break; } } } // If we couldn't find a good spot, append before the footer if (insertIndex === -1) { const footerIndex = lines.findIndex(line => line.trim() === '---'); insertIndex = footerIndex !== -1 ? footerIndex : lines.length; } // Insert the section const sectionLines = sectionContent.split('\n'); lines.splice(insertIndex, 0, '', ...sectionLines); return lines.join('\n'); } /** * Updates a file description with better content */ function updateFileDescription(content, issue) { const fileName = issue.suggestedFix.target; const newDescription = issue.suggestedFix.content; const lines = content.split('\n'); // Find the line with the file description for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.includes(`\`${fileName}\``)) { // Replace the description part after the filename const match = line.match(/^(- `[^`]+` - ).+$/); if (match) { lines[i] = match[1] + newDescription; break; } } } return lines.join('\n'); } /** * Removes placeholder content and replaces with better content */ function removePlaceholderContent(content, issue) { const placeholder = issue.suggestedFix.target; // For now, just remove the placeholder // In a more sophisticated version, we'd replace with meaningful content let modifiedContent = content; if (placeholder.startsWith('[') && placeholder.includes('description')) { // Remove placeholder descriptions modifiedContent = content.replace(placeholder, 'Content to be documented'); } else { // Remove references to deleted files const lines = content.split('\n'); const filteredLines = lines.filter(line => !line.includes(placeholder)); modifiedContent = filteredLines.join('\n'); } return modifiedContent; } /** * Creates a summary report of all fixes applied */ export function createFixReport(fixSummaries) { const totalFiles = fixSummaries.length; const totalIssues = fixSummaries.reduce((sum, s) => sum + s.totalIssues, 0); const totalFixes = fixSummaries.reduce((sum, s) => sum + s.fixesApplied, 0); const totalSkipped = fixSummaries.reduce((sum, s) => sum + s.fixesSkipped, 0); let report = `📊 Documentation Fix Report\n`; report += `${'='.repeat(40)}\n\n`; report += `Files processed: ${totalFiles}\n`; report += `Total issues found: ${totalIssues}\n`; report += `Fixes applied: ${totalFixes}\n`; report += `Fixes skipped: ${totalSkipped}\n\n`; for (const summary of fixSummaries) { if (summary.fixesApplied > 0 || summary.fixesSkipped > 0) { report += `📄 ${path.basename(summary.filePath)}\n`; for (const result of summary.results) { const status = result.applied ? '✅' : '⏭️ '; const reason = result.reason ? ` (${result.reason})` : ''; report += ` ${status} ${result.issue.description}${reason}\n`; } report += `\n`; } } return report; } //# sourceMappingURL=documentationFixer.js.map