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
268 lines • 9.93 kB
JavaScript
/**
* Audit Runner (Functional)
* Main orchestrator for running code audits
*/
import { discoverFiles } from './utils/fileDiscovery.js';
import { loadConfig } from './config/configLoader.js';
import { generateReport } from './reporting/reportGenerator.js';
import { extractFunctionsFromFile } from './functionScanner.js';
// Import analyzer definitions
// Use compatibility layers for refactored analyzers
import { createSOLIDAnalyzer } from './adapters/solidAnalyzerCompat.js';
import { dryAnalyzer } from './analyzers/dryAnalyzerCompat.js';
// import { securityAnalyzer } from './analyzers/securityAnalyzer.js';
// import { componentAnalyzer } from './analyzers/componentAnalyzer.js';
import { dataAccessAnalyzer } from './analyzers/dataAccessAnalyzerCompat.js';
import { reactAnalyzer } from './analyzers/reactAnalyzer.js'; // Keep legacy React analyzer
import { documentationAnalyzer } from './analyzers/documentationAnalyzerCompat.js';
import { schemaAnalyzer } from './analyzers/schemaAnalyzerCompat.js';
/**
* Default analyzer registry
*/
const DEFAULT_ANALYZERS = {
'solid': createSOLIDAnalyzer(),
'dry': dryAnalyzer,
// 'security': securityAnalyzer,
// 'component': componentAnalyzer,
'data-access': dataAccessAnalyzer,
'react': reactAnalyzer,
'documentation': documentationAnalyzer,
'schema': schemaAnalyzer
};
/**
* Create an audit runner with the given options
*/
export function createAuditRunner(options = {}) {
const analyzerRegistry = { ...DEFAULT_ANALYZERS };
/**
* Register a custom analyzer
*/
function registerAnalyzer(analyzer) {
analyzerRegistry[analyzer.name] = analyzer;
}
/**
* Load configuration from file
*/
async function loadConfiguration(configPath) {
const config = await loadConfig({ configPath });
return { ...options, ...config };
}
/**
* Run the audit
*/
async function run(runOptions) {
const mergedOptions = { ...options, ...runOptions };
const startTime = Date.now();
// Report progress
reportProgress(mergedOptions, {
phase: 'discovery',
message: 'Discovering files...'
});
// Discover files
const files = await discoverProjectFiles(mergedOptions);
// Collect functions if enabled
let collectedFunctions = [];
const fileToFunctionsMap = new Map(); // Track functions per file for sync
if (mergedOptions.indexFunctions) {
reportProgress(mergedOptions, {
phase: 'function-indexing',
message: 'Collecting functions from files...'
});
// Collect functions from TypeScript/JavaScript files
// Note: Go files will be indexed by the Universal SOLID analyzer directly
const scriptFiles = files.filter(f => f.endsWith('.ts') || f.endsWith('.tsx') ||
f.endsWith('.js') || f.endsWith('.jsx'));
for (let i = 0; i < scriptFiles.length; i++) {
try {
const fileFunctions = await extractFunctionsFromFile(scriptFiles[i], {
unusedImportsConfig: mergedOptions.unusedImportsConfig
});
collectedFunctions.push(...fileFunctions);
fileToFunctionsMap.set(scriptFiles[i], fileFunctions); // Store for sync
reportProgress(mergedOptions, {
phase: 'function-indexing',
current: i + 1,
total: scriptFiles.length,
message: `Collected ${fileFunctions.length} items from ${scriptFiles[i]}`
});
}
catch (error) {
// Log error but continue with other files
console.warn(`Failed to extract functions from ${scriptFiles[i]}:`, error);
}
}
}
// Run analyzers
const analyzerResults = {};
const enabledAnalyzers = getEnabledAnalyzers(mergedOptions, analyzerRegistry);
console.log('[DEBUG] Enabled analyzers:', enabledAnalyzers);
console.log('[DEBUG] Available analyzers in registry:', Object.keys(analyzerRegistry));
for (const analyzerName of enabledAnalyzers) {
const analyzer = analyzerRegistry[analyzerName];
console.log(`[DEBUG] Processing analyzer: ${analyzerName}, found: ${!!analyzer}`);
if (!analyzer) {
console.warn(`Unknown analyzer: ${analyzerName}`);
continue;
}
reportProgress(mergedOptions, {
phase: 'analysis',
analyzer: analyzerName,
message: `Running ${analyzerName} analyzer...`
});
console.log(`[DEBUG] About to call ${analyzerName}.analyze()`);
console.log(`[DEBUG] Analyzer object:`, analyzer);
console.log(`[DEBUG] Analyzer.analyze type:`, typeof analyzer.analyze);
console.log(`[DEBUG] Files to analyze:`, files);
console.log(`[DEBUG] Config for analyzer:`, mergedOptions.analyzerConfigs?.[analyzerName] || {});
try {
const result = await analyzer.analyze(files, mergedOptions.analyzerConfigs?.[analyzerName] || {}, mergedOptions, (progress) => {
reportProgress(mergedOptions, {
phase: 'analysis',
analyzer: analyzerName,
current: progress.current,
total: progress.total,
message: `Analyzing ${progress.file}...`
});
});
console.log(`[DEBUG] ${analyzerName}.analyze() returned:`, result);
analyzerResults[analyzerName] = result;
}
catch (error) {
reportError(mergedOptions, error, `${analyzerName} analyzer`);
analyzerResults[analyzerName] = {
violations: [],
filesProcessed: 0,
executionTime: 0,
errors: [{ file: 'analyzer', error: error.message }]
};
}
}
// Generate summary
const summary = generateSummary(analyzerResults);
// Create result
const result = {
timestamp: new Date(),
summary,
analyzerResults,
recommendations: [],
metadata: {
auditDuration: Date.now() - startTime,
filesAnalyzed: files.length,
analyzersRun: enabledAnalyzers,
configUsed: mergedOptions,
...(collectedFunctions.length > 0 && {
collectedFunctions,
fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap)
})
}
};
// Report completion
reportProgress(mergedOptions, {
phase: 'reporting',
message: 'Generating reports...'
});
return result;
}
/**
* Generate report in specified format
*/
async function generateReportForResult(result, format) {
return generateReport(result, format);
}
return {
registerAnalyzer,
loadConfiguration,
run,
generateReport: generateReportForResult
};
}
/**
* Discover files to analyze
*/
async function discoverProjectFiles(options) {
const rootDir = options.projectRoot || process.cwd();
return discoverFiles(rootDir, {
includePaths: options.includePaths,
excludePaths: options.excludePaths,
extensions: options.fileExtensions, // Use override if provided
excludeDirs: undefined // This will use DEFAULT_EXCLUDED_DIRS which includes node_modules
});
}
/**
* Get list of enabled analyzers
*/
function getEnabledAnalyzers(options, registry) {
if (options.enabledAnalyzers && options.enabledAnalyzers.length > 0) {
return options.enabledAnalyzers;
}
return Object.keys(registry);
}
/**
* Generate audit summary
*/
function generateSummary(analyzerResults) {
let totalViolations = 0;
let criticalIssues = 0;
let warnings = 0;
let suggestions = 0;
const violationsByCategory = {};
for (const [analyzer, result] of Object.entries(analyzerResults)) {
for (const violation of result.violations) {
totalViolations++;
switch (violation.severity) {
case 'critical':
criticalIssues++;
break;
case 'warning':
warnings++;
break;
case 'suggestion':
suggestions++;
break;
}
const category = violation.type || analyzer;
violationsByCategory[category] = (violationsByCategory[category] || 0) + 1;
}
}
return {
totalFiles: 0,
totalViolations,
criticalIssues,
warnings,
suggestions,
violationsByCategory,
topIssues: []
};
}
/**
* Report progress
*/
function reportProgress(options, progress) {
if (options.progressCallback) {
options.progressCallback({
current: 0,
total: 0,
analyzer: '',
...progress
});
}
}
/**
* Report error
*/
function reportError(options, error, context) {
if (options.errorCallback) {
options.errorCallback(error, context);
}
else {
console.error(`Error in ${context}:`, error);
}
}
/**
* Run audit with default runner (convenience function)
*/
export async function runAudit(options) {
const runner = createAuditRunner(options);
return runner.run();
}
//# sourceMappingURL=auditRunner.js.map