UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

281 lines (244 loc) 8.74 kB
/** * @file Comment analysis and documentation quality assessment * @description Single responsibility: Analyze code comments and documentation coverage * * This module provides comprehensive comment analysis including documentation quality assessment, * comment coverage metrics, and actionable recommendations for improving code documentation. * It analyzes both single files and entire projects to identify areas needing better documentation. */ const fs = require('fs').promises; const path = require('path'); const { getAllFiles } = require('../utils/directoryScanner'); /** * Analyze comments in a single file * @param {string} filePath - Path to the file to analyze * @returns {Promise<Object>} Comment analysis results */ async function analyzeFileComments(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); const lines = content.split('\n'); const analysis = { file: filePath, totalLines: lines.length, commentLines: 0, docBlocks: 0, inlineComments: 0, todoComments: 0, functions: 0, documentedFunctions: 0, classes: 0, documentedClasses: 0, coverage: 0, score: 0, issues: [] }; let inDocBlock = false; let currentFunction = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Count comment types if (line.startsWith('/**')) { analysis.docBlocks++; analysis.commentLines++; inDocBlock = true; } else if (line.startsWith('*/')) { inDocBlock = false; analysis.commentLines++; } else if (inDocBlock) { analysis.commentLines++; } else if (line.startsWith('//')) { analysis.commentLines++; analysis.inlineComments++; if (line.toLowerCase().includes('todo') || line.toLowerCase().includes('fixme')) { analysis.todoComments++; } } // Detect functions and classes if (line.includes('function ') || line.includes(') => ') || line.includes('function(')) { analysis.functions++; // Check if previous lines contain documentation const prevLines = lines.slice(Math.max(0, i - 5), i); const hasDocumentation = prevLines.some(l => l.trim().startsWith('/**') || l.trim().startsWith('*')); if (hasDocumentation) { analysis.documentedFunctions++; } else { analysis.issues.push({ type: 'missing_function_doc', line: i + 1, message: 'Function lacks documentation' }); } } if (line.includes('class ')) { analysis.classes++; // Check for class documentation const prevLines = lines.slice(Math.max(0, i - 5), i); const hasDocumentation = prevLines.some(l => l.trim().startsWith('/**')); if (hasDocumentation) { analysis.documentedClasses++; } else { analysis.issues.push({ type: 'missing_class_doc', line: i + 1, message: 'Class lacks documentation' }); } } } // Calculate coverage and score analysis.coverage = analysis.totalLines > 0 ? (analysis.commentLines / analysis.totalLines) * 100 : 0; const functionCoverage = analysis.functions > 0 ? (analysis.documentedFunctions / analysis.functions) * 100 : 100; const classCoverage = analysis.classes > 0 ? (analysis.documentedClasses / analysis.classes) * 100 : 100; analysis.score = Math.round((analysis.coverage * 0.4 + functionCoverage * 0.4 + classCoverage * 0.2)); return analysis; } catch (error) { return { file: filePath, error: error.message, score: 0 }; } } /** * Analyze comments across an entire project * @param {string} projectPath - Path to the project directory * @param {Object} options - Analysis options * @returns {Promise<Object>} Project-wide comment analysis */ async function analyzeProjectComments(projectPath, options = {}) { const extensions = options.extensions || ['.js', '.ts', '.jsx', '.tsx']; try { const files = await getAllFiles(projectPath, extensions); const results = []; const summary = { totalFiles: files.length, analyzedFiles: 0, averageScore: 0, totalLines: 0, totalCommentLines: 0, totalFunctions: 0, documentedFunctions: 0, totalClasses: 0, documentedClasses: 0, issues: [] }; // Analyze each file for (const file of files) { const analysis = await analyzeFileComments(file); if (!analysis.error) { results.push(analysis); summary.analyzedFiles++; summary.totalLines += analysis.totalLines; summary.totalCommentLines += analysis.commentLines; summary.totalFunctions += analysis.functions; summary.documentedFunctions += analysis.documentedFunctions; summary.totalClasses += analysis.classes; summary.documentedClasses += analysis.documentedClasses; summary.issues.push(...analysis.issues); } } // Calculate project averages if (summary.analyzedFiles > 0) { summary.averageScore = Math.round( results.reduce((sum, r) => sum + r.score, 0) / summary.analyzedFiles ); } summary.overallCoverage = summary.totalLines > 0 ? (summary.totalCommentLines / summary.totalLines) * 100 : 0; summary.functionCoverage = summary.totalFunctions > 0 ? (summary.documentedFunctions / summary.totalFunctions) * 100 : 100; summary.classCoverage = summary.totalClasses > 0 ? (summary.documentedClasses / summary.totalClasses) * 100 : 100; return { summary, files: results }; } catch (error) { throw new Error(`Project comment analysis failed: ${error.message}`); } } /** * Calculate documentation score for a file or project * @param {Object} analysis - Analysis results * @returns {number} Documentation score (0-100) */ function calculateDocumentationScore(analysis) { if (analysis.summary) { // Project-level score return analysis.summary.averageScore; } else { // File-level score return analysis.score || 0; } } /** * Generate recommendations for improving documentation * @param {Object} analysis - Analysis results * @returns {Array} Array of recommendations */ function generateCommentRecommendations(analysis) { const recommendations = []; if (analysis.summary) { // Project-level recommendations const summary = analysis.summary; if (summary.averageScore < 60) { recommendations.push({ priority: 'HIGH', type: 'overall_documentation', message: `Project documentation score is low (${summary.averageScore}/100). Consider establishing documentation standards.` }); } if (summary.functionCoverage < 70) { recommendations.push({ priority: 'HIGH', type: 'function_documentation', message: `Only ${Math.round(summary.functionCoverage)}% of functions are documented. Add JSDoc comments to improve maintainability.` }); } if (summary.classCoverage < 80) { recommendations.push({ priority: 'MEDIUM', type: 'class_documentation', message: `${Math.round(summary.classCoverage)}% of classes have documentation. Consider adding class-level comments.` }); } if (summary.overallCoverage < 15) { recommendations.push({ priority: 'MEDIUM', type: 'comment_coverage', message: `Comment coverage is ${Math.round(summary.overallCoverage)}%. Consider adding more explanatory comments.` }); } } else { // File-level recommendations if (analysis.score < 60) { recommendations.push({ priority: 'HIGH', type: 'file_documentation', message: 'File needs better documentation coverage' }); } if (analysis.functions > analysis.documentedFunctions) { recommendations.push({ priority: 'HIGH', type: 'function_docs', message: `${analysis.functions - analysis.documentedFunctions} functions need documentation` }); } if (analysis.todoComments > 0) { recommendations.push({ priority: 'LOW', type: 'todo_cleanup', message: `${analysis.todoComments} TODO/FIXME comments should be addressed` }); } } return recommendations; } module.exports = { analyzeFileComments, analyzeProjectComments, calculateDocumentationScore, generateCommentRecommendations };