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
JavaScript
/**
* 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