UNPKG

agentsqripts

Version:

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

426 lines (358 loc) 13.8 kB
/** * @file Unit tests for comment analysis functionality * @description Tests comment quality analysis, documentation coverage, and improvement recommendations */ const { analyzeFileComments, analyzeProjectComments, calculateDocumentationScore, generateCommentRecommendations } = require('./analyzeComments'); const qtests = require('qtests'); const fs = require('fs'); const path = require('path'); /** * Test runner for comment analysis */ async function runTests() { console.log('=== Testing Comment Analysis Functionality ==='); const results = { total: 0, passed: 0 }; // Test analysis of well-documented code results.total++; try { const tempFile = path.join(__dirname, 'temp-documented-test.js'); const documentedCode = ` /** * @file User management utilities * @description Provides functions for user validation and processing * @author Developer * @since 1.0.0 */ /** * Validates user input data * @param {Object} user - The user object to validate * @param {string} user.name - The user's name * @param {string} user.email - The user's email address * @returns {boolean} True if user is valid, false otherwise * @throws {Error} When user object is null or undefined * @example * const isValid = validateUser({ name: 'John', email: 'john@example.com' }); */ function validateUser(user) { if (!user) { throw new Error('User object is required'); } // Check if name is provided and valid if (!user.name || typeof user.name !== 'string') { return false; } // Validate email format using regex const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; return emailRegex.test(user.email); } /** * Processes user data for storage * @param {Object} user - Raw user data * @returns {Object} Processed user data ready for database */ function processUser(user) { return { name: user.name.trim(), email: user.email.toLowerCase(), createdAt: new Date() }; } `; fs.writeFileSync(tempFile, documentedCode); const analysis = await analyzeFileComments(tempFile); qtests.assert(typeof analysis === 'object', 'analyzeFileComments should return object'); qtests.assert(typeof analysis.summary === 'object', 'Analysis should include summary'); qtests.assert(typeof analysis.summary.documentationScore === 'number', 'Should calculate documentation score'); qtests.assert(analysis.summary.documentationScore > 80, 'Well-documented code should have high score'); qtests.assert(analysis.summary.totalFunctions > 0, 'Should count functions'); qtests.assert(analysis.summary.documentedFunctions > 0, 'Should count documented functions'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileComments correctly analyzes well-documented code'); results.passed++; } catch (error) { console.log(`✗ analyzeFileComments documented code test failed: ${error.message}`); } // Test analysis of poorly documented code results.total++; try { const tempFile = path.join(__dirname, 'temp-undocumented-test.js'); const undocumentedCode = ` // Poor documentation examples function calc(a, b) { return a + b; } // TODO: add validation function process(data) { // some logic here let result = data.map(x => x * 2); return result; } function helper() { // FIXME: this is broken return Math.random(); } // No JSDoc, unclear purpose const mystery = (x, y, z) => { if (x > y) { return z(x); } return y; }; function undocumentedComplexFunction(config, options, callback) { const settings = { ...config, ...options }; const result = settings.items.filter(item => item.active && item.type === 'premium' ).map(item => ({ id: item.id, name: item.name, value: item.price * settings.multiplier })); if (callback) { callback(null, result); } return result; } `; fs.writeFileSync(tempFile, undocumentedCode); const analysis = await analyzeFileComments(tempFile); qtests.assert(analysis.summary.documentationScore < 50, 'Poorly documented code should have low score'); qtests.assert(analysis.issues.length > 0, 'Should identify documentation issues'); qtests.assert(analysis.issues.some(issue => issue.type.includes('missing_documentation') || issue.category === 'Documentation' ), 'Should identify missing documentation'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileComments correctly identifies poorly documented code'); results.passed++; } catch (error) { console.log(`✗ analyzeFileComments undocumented code test failed: ${error.message}`); } // Test comment quality analysis results.total++; try { const tempFile = path.join(__dirname, 'temp-comment-quality-test.js'); const commentCode = ` // Good comment: explains why, not what function calculateTax(amount, rate) { // Apply tax rate with rounding to avoid floating point errors return Math.round(amount * rate * 100) / 100; } // Bad comment: states the obvious function addNumbers(a, b) { // Add a and b together return a + b; } // Outdated comment (misleading) function getUsers() { // Returns array of user IDs return users.map(user => ({ id: user.id, name: user.name })); // Actually returns objects } // Commented out code (code smell) function processData(data) { // const oldWay = data.filter(x => x.valid); // return oldWay.map(x => x.value); // New approach using reduce return data.reduce((acc, item) => { if (item.valid) { acc.push(item.value); } return acc; }, []); } // TODO and FIXME comments function incompleteFunction() { // TODO: implement error handling // FIXME: memory leak in loop // HACK: temporary workaround return 'placeholder'; } `; fs.writeFileSync(tempFile, commentCode); const analysis = await analyzeFileComments(tempFile); qtests.assert(analysis.issues.some(issue => issue.type.includes('obvious_comment') || issue.type.includes('outdated_comment') || issue.type.includes('commented_code') ), 'Should identify comment quality issues'); qtests.assert(analysis.summary.todoCount > 0, 'Should count TODO comments'); qtests.assert(analysis.summary.fixmeCount > 0, 'Should count FIXME comments'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileComments correctly analyzes comment quality'); results.passed++; } catch (error) { console.log(`✗ analyzeFileComments comment quality test failed: ${error.message}`); } // Test JSDoc validation results.total++; try { const tempFile = path.join(__dirname, 'temp-jsdoc-test.js'); const jsdocCode = ` /** * Valid JSDoc with all required elements * @param {string} name - User name * @param {number} age - User age * @returns {Object} User object */ function createUser(name, age) { return { name, age }; } /** * Invalid JSDoc - missing @param for second parameter * @param {string} title - Document title * @returns {Document} New document */ function createDocument(title, content) { return { title, content }; } /** * Missing @returns annotation * @param {Array} items - List of items */ function processItems(items) { return items.filter(item => item.active); } /** * Incorrect type annotation * @param {string} numbers - Should be array of numbers * @returns {number} Sum of numbers */ function sumNumbers(numbers) { return numbers.reduce((sum, num) => sum + num, 0); } `; fs.writeFileSync(tempFile, jsdocCode); const analysis = await analyzeFileComments(tempFile); qtests.assert(analysis.issues.some(issue => issue.type.includes('jsdoc_mismatch') || issue.type.includes('missing_param') || issue.type.includes('missing_returns') ), 'Should identify JSDoc validation issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileComments correctly validates JSDoc comments'); results.passed++; } catch (error) { console.log(`✗ analyzeFileComments JSDoc validation test failed: ${error.message}`); } // Test documentation score calculation results.total++; try { const testMetrics = { totalFunctions: 10, documentedFunctions: 7, totalClasses: 3, documentedClasses: 2, jsdocQuality: 0.8, commentQuality: 0.7, todoFixmeCount: 2 }; const score = calculateDocumentationScore(testMetrics); qtests.assert(typeof score === 'number', 'calculateDocumentationScore should return number'); qtests.assert(score >= 0 && score <= 100, 'Documentation score should be between 0 and 100'); qtests.assert(score > 50 && score < 90, 'Score should reflect mixed documentation quality'); // Test perfect documentation const perfectMetrics = { totalFunctions: 5, documentedFunctions: 5, totalClasses: 2, documentedClasses: 2, jsdocQuality: 1.0, commentQuality: 1.0, todoFixmeCount: 0 }; const perfectScore = calculateDocumentationScore(perfectMetrics); qtests.assert(perfectScore >= 90, 'Perfect documentation should score 90+'); console.log('✓ calculateDocumentationScore correctly calculates scores'); results.passed++; } catch (error) { console.log(`✗ calculateDocumentationScore test failed: ${error.message}`); } // Test comment recommendations generation results.total++; try { const testIssues = [ { type: 'missing_documentation', severity: 'MEDIUM', functionName: 'processData', line: 15 }, { type: 'obvious_comment', severity: 'LOW', comment: '// Add a and b together', line: 25 }, { type: 'jsdoc_mismatch', severity: 'HIGH', expectedParams: ['title', 'content'], actualParams: ['title'], line: 35 } ]; const recommendations = generateCommentRecommendations(testIssues); qtests.assert(Array.isArray(recommendations), 'generateCommentRecommendations should return array'); qtests.assert(recommendations.length > 0, 'Should generate recommendations for comment issues'); qtests.assert(recommendations.every(rec => typeof rec.priority === 'string'), 'Each recommendation should have priority'); qtests.assert(recommendations.every(rec => typeof rec.suggestion === 'string'), 'Each recommendation should have suggestion'); qtests.assert(recommendations.every(rec => typeof rec.example === 'string'), 'Each recommendation should have example'); console.log('✓ generateCommentRecommendations correctly generates actionable recommendations'); results.passed++; } catch (error) { console.log(`✗ generateCommentRecommendations test failed: ${error.message}`); } // Test project-level comment analysis results.total++; try { const tempDir = path.join(__dirname, 'temp-comment-project'); fs.mkdirSync(tempDir, { recursive: true }); // Create multiple files with varying documentation quality const goodFile = path.join(tempDir, 'well-documented.js'); fs.writeFileSync(goodFile, ` /** * @file Well documented module */ /** * Calculates area of rectangle * @param {number} width - Rectangle width * @param {number} height - Rectangle height * @returns {number} Area of rectangle */ function calculateArea(width, height) { return width * height; } `); const poorFile = path.join(tempDir, 'poorly-documented.js'); fs.writeFileSync(poorFile, ` function doSomething(x, y) { return x + y; } const another = (a) => a * 2; `); const projectAnalysis = await analyzeProjectComments(tempDir, { extensions: ['.js'] }); qtests.assert(typeof projectAnalysis === 'object', 'analyzeProjectComments should return object'); qtests.assert(projectAnalysis.summary.totalFiles >= 2, 'Should analyze multiple files'); qtests.assert(typeof projectAnalysis.summary.overallScore === 'number', 'Should calculate overall documentation score'); // Clean up fs.rmSync(tempDir, { recursive: true, force: true }); console.log('✓ analyzeProjectComments correctly analyzes project-level documentation'); results.passed++; } catch (error) { console.log(`✗ analyzeProjectComments test failed: ${error.message}`); } console.log(`=== Comment Analysis Test Results ===`); console.log(`Tests passed: ${results.passed}/${results.total}`); console.log(`Success rate: ${((results.passed / results.total) * 100).toFixed(1)}%`); return results; } module.exports = { runTests };