UNPKG

code-auditor-mcp

Version:

Multi-language code quality auditor with MCP server - Analyze TypeScript, JavaScript, and Go code for SOLID principles, DRY violations, security patterns, and more

175 lines 7.88 kB
/** * Universal Documentation Analyzer * Works across multiple programming languages using the adapter pattern */ import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js'; export const DEFAULT_DOCUMENTATION_CONFIG = { requireFunctionDocs: true, requireClassDocs: true, requireFileDocs: true, requireParamDocs: true, requireReturnDocs: true, minDescriptionLength: 10, checkExportedOnly: false, exemptPatterns: [ '\\.test\\.', // files like user.test.ts '\\.spec\\.', // files like user.spec.ts '\\.d\\.ts$', // TypeScript declaration files 'mock', // mock files 'fixture', // fixture files '__tests__', // __tests__ directories '/tests?/', // /test/ or /tests/ directories ] }; export class UniversalDocumentationAnalyzer extends UniversalAnalyzer { name = 'documentation'; description = 'Analyzes documentation quality across the codebase'; category = 'documentation'; async analyzeAST(ast, adapter, config, sourceCode) { console.log('[DEBUG] UniversalDocumentationAnalyzer.analyzeAST called for:', ast.filePath); const violations = []; const finalConfig = { ...DEFAULT_DOCUMENTATION_CONFIG, ...config }; console.log('[DEBUG] Documentation config:', finalConfig); // Check if file is exempt if (this.isExempt(ast.filePath, finalConfig.exemptPatterns)) { console.log('[DEBUG] File is exempt from documentation analysis:', ast.filePath); return violations; } console.log('[DEBUG] File is NOT exempt, proceeding with analysis:', ast.filePath); // Check file-level documentation if (finalConfig.requireFileDocs) { const fileDoc = this.getFileDocumentation(ast, adapter); if (!fileDoc || fileDoc.length < finalConfig.minDescriptionLength) { violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'File lacks proper documentation header', 'warning', 'file-documentation')); } } // Check function documentation if (finalConfig.requireFunctionDocs) { const functions = adapter.extractFunctions(ast); console.log('[DEBUG] Extracted functions count:', functions.length); for (const func of functions) { // Skip if checking exported only and function is not exported if (finalConfig.checkExportedOnly && !func.isExported) { continue; } // Skip if function name matches exempt pattern if (this.isExempt(func.name, finalConfig.exemptPatterns)) { continue; } const doc = func.jsDoc || ''; // Debug logging console.log('[DEBUG] Checking function:', { name: func.name, jsDoc: func.jsDoc, docLength: doc.length, minLength: finalConfig.minDescriptionLength }); // Check if documentation exists and is adequate if (!doc || doc.length < finalConfig.minDescriptionLength) { violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' lacks proper documentation`, 'warning', 'function-documentation')); } else if (finalConfig.requireParamDocs && func.parameters.length > 0) { // Check parameter documentation const missingParamDocs = this.checkParameterDocumentation(doc, func.parameters.map(p => p.name)); for (const param of missingParamDocs) { violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' missing documentation for parameter '${param}'`, 'suggestion', 'parameter-documentation')); } } // Check return documentation for non-void functions if (finalConfig.requireReturnDocs && func.returnType && func.returnType !== 'void' && !this.hasReturnDocumentation(doc)) { violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' missing return value documentation`, 'suggestion', 'return-documentation')); } } } // Check class documentation if (finalConfig.requireClassDocs) { const classes = adapter.extractClasses(ast); for (const cls of classes) { // Skip if checking exported only and class is not exported if (finalConfig.checkExportedOnly && !cls.isExported) { continue; } // Skip if class name matches exempt pattern if (this.isExempt(cls.name, finalConfig.exemptPatterns)) { continue; } const doc = cls.jsDoc || ''; if (!doc || doc.length < finalConfig.minDescriptionLength) { violations.push(this.createViolation(ast.filePath, cls.location.start, `Class '${cls.name}' lacks proper documentation`, 'warning', 'class-documentation')); } // Check method documentation if (finalConfig.requireFunctionDocs) { for (const method of cls.methods) { const methodDoc = method.jsDoc || ''; if (!methodDoc || methodDoc.length < finalConfig.minDescriptionLength) { violations.push(this.createViolation(ast.filePath, method.location.start, `Method '${cls.name}.${method.name}' lacks proper documentation`, 'warning', 'method-documentation')); } } } } } return violations; } /** * Check if a name matches any exempt patterns */ isExempt(name, patterns) { return patterns.some(pattern => { const regex = new RegExp(pattern, 'i'); return regex.test(name); }); } /** * Get file-level documentation (usually at the top) */ getFileDocumentation(ast, adapter) { // Look for documentation at the beginning of the file const firstChild = ast.root.children?.[0]; if (firstChild) { return adapter.getDocumentation(firstChild); } return null; } /** * Find a node by its location */ findNodeByLocation(root, location) { const queue = [root]; while (queue.length > 0) { const node = queue.shift(); if (node.location.start.line === location.line && node.location.start.column === location.column) { return node; } if (node.children) { queue.push(...node.children); } } return null; } /** * Check which parameters are missing documentation */ checkParameterDocumentation(doc, paramNames) { const missingParams = []; for (const param of paramNames) { // Look for @param tags in various formats const paramRegex = new RegExp(`@param\\s+(?:\\{[^}]+\\}\\s+)?${param}\\b`, 'i'); if (!paramRegex.test(doc)) { missingParams.push(param); } } return missingParams; } /** * Check if documentation contains return value documentation */ hasReturnDocumentation(doc) { // Look for @return or @returns tags return /@returns?\b/i.test(doc); } } //# sourceMappingURL=UniversalDocumentationAnalyzer.js.map