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
JavaScript
/**
* @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 };