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

188 lines 5.93 kB
/** * Functional utilities for analyzer development * These replace the BaseAnalyzer class with composable functions */ import * as ts from 'typescript'; // Re-export utilities from other modules export { parseTypeScriptFile } from '../utils/astParser.js'; export { getLineAndColumn, getNodeText, getImports, findNodesByKind } from '../utils/astUtils.js'; /** * Standard file processing function * Handles file reading, parsing, error handling, and progress reporting */ export async function processFiles(files, analyzeFile, analyzerName, config = {}, progressReporter) { const violations = []; const errors = []; let processedFiles = 0; const startTime = Date.now(); for (const file of files) { try { // Report progress if (progressReporter) { progressReporter(processedFiles, files.length, file); } // Read and parse file const { parseTypeScriptFile: parse } = await import('../utils/astParser.js'); const { sourceFile, errors: parseErrors } = await parse(file); if (parseErrors.length > 0) { throw new Error(`Parse errors: ${parseErrors.map(e => e.messageText).join(', ')}`); } // Run analyzer-specific logic const fileViolations = await analyzeFile(file, sourceFile, config); violations.push(...fileViolations); processedFiles++; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); errors.push({ file, error: errorMessage }); console.error(`Error analyzing ${file}:`, error); } } const result = { violations, filesProcessed: processedFiles, executionTime: Date.now() - startTime, analyzerName }; if (errors.length > 0) { result.errors = errors; } return result; } /** * Create a violation object with defaults */ export function createViolation(data) { return data; } /** * Get line and column from a TypeScript node */ export function getNodePosition(sourceFile, node) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); return { line: line + 1, column: character + 1 }; } /** * Check if a node is exported */ export function isNodeExported(node) { if (ts.canHaveModifiers(node)) { const modifiers = ts.getModifiers(node); return !!modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword); } return false; } /** * Get the name of a node if it has one */ export function getNodeName(node) { if ('name' in node && node.name) { const name = node.name; if (ts.isIdentifier(name)) { return name.text; } } return undefined; } /** * Count specific node types in a subtree */ export function countNodesOfType(node, predicate) { let count = 0; const visit = (node) => { if (predicate(node)) { count++; } ts.forEachChild(node, visit); }; visit(node); return count; } /** * Find all nodes of a specific type */ export function findNodesOfType(node, predicate) { const nodes = []; const visit = (node) => { if (predicate(node)) { nodes.push(node); } ts.forEachChild(node, visit); }; visit(node); return nodes; } /** * Traverse AST with a visitor function */ export function traverseAST(node, visitor) { visitor(node); ts.forEachChild(node, child => traverseAST(child, visitor)); } /** * Filter violations by severity */ export function filterViolationsBySeverity(violations, minSeverity) { if (!minSeverity) { return violations; } const severityOrder = { critical: 3, warning: 2, suggestion: 1 }; const minLevel = severityOrder[minSeverity] || 0; return violations.filter(v => severityOrder[v.severity] >= minLevel); } /** * Sort violations by severity, file, and line */ export function sortViolations(violations) { return violations.sort((a, b) => { // Sort by severity first const severityOrder = { critical: 3, warning: 2, suggestion: 1 }; const severityDiff = severityOrder[b.severity] - severityOrder[a.severity]; if (severityDiff !== 0) return severityDiff; // Then by file const fileDiff = a.file.localeCompare(b.file); if (fileDiff !== 0) return fileDiff; // Then by line return (a.line || 0) - (b.line || 0); }); } /** * Calculate cyclomatic complexity of a function */ export function calculateComplexity(node) { let complexity = 1; traverseAST(node, (child) => { if (ts.isIfStatement(child) || ts.isConditionalExpression(child) || ts.isSwitchStatement(child) || ts.isForStatement(child) || ts.isWhileStatement(child) || ts.isDoStatement(child) || ts.isCaseClause(child)) { complexity++; } if (ts.isBinaryExpression(child)) { const operator = child.operatorToken.kind; if (operator === ts.SyntaxKind.AmpersandAmpersandToken || operator === ts.SyntaxKind.BarBarToken) { complexity++; } } }); return complexity; } /** * Create a standard analyzer function */ export function createAnalyzer(name, fileAnalyzer, defaultConfig = {}) { return async (files, config, options) => { const mergedConfig = { ...defaultConfig, ...config }; const result = await processFiles(files, fileAnalyzer, name, mergedConfig); // Apply filtering and sorting result.violations = sortViolations(filterViolationsBySeverity(result.violations, options?.minSeverity)); return result; }; } //# sourceMappingURL=analyzerUtils.js.map