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

214 lines 6.4 kB
/** * File Discovery Utilities * Provides functionality for discovering and filtering files for analysis * * Supports TypeScript, JavaScript, and JSX/TSX files with configurable * include/exclude patterns */ import { promises as fs } from 'fs'; import path from 'path'; // Default directories to exclude from analysis export const DEFAULT_EXCLUDED_DIRS = [ 'node_modules', '.next', 'dist', 'build', '.git', 'coverage', '.turbo', 'out', '.cache', 'tmp', 'temp', '.vscode', '.idea' ]; // Supported file extensions export const TYPESCRIPT_EXTENSIONS = ['.ts', '.tsx']; export const JAVASCRIPT_EXTENSIONS = ['.js', '.jsx']; export const JSON_EXTENSIONS = ['.json']; export const GO_EXTENSIONS = ['.go']; export const ALL_EXTENSIONS = [...TYPESCRIPT_EXTENSIONS, ...JAVASCRIPT_EXTENSIONS, ...JSON_EXTENSIONS, ...GO_EXTENSIONS]; /** * Check if a path should be excluded based on directory names */ function shouldExcludeDir(filePath, excludeDirs) { const parts = filePath.split(path.sep); return parts.some(part => excludeDirs.includes(part)); } /** * Recursively find files matching criteria */ async function findFilesRecursive(dir, options) { const results = []; try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (shouldExcludeDir(fullPath, options.excludeDirs)) { continue; } if (entry.isDirectory()) { const subResults = await findFilesRecursive(fullPath, options); results.push(...subResults); } else if (entry.isFile()) { const ext = path.extname(entry.name); if (options.extensions.includes(ext)) { if (!options.pattern || options.pattern.test(entry.name)) { results.push(fullPath); } } } } } catch (error) { // Silently skip directories we can't read if (error.code !== 'EACCES') { console.error(`Error reading directory ${dir}:`, error); } } return results; } /** * Find all files matching the given options */ export async function findFiles(rootDir = process.cwd(), options = {}) { const extensions = options.extensions || ALL_EXTENSIONS; const excludeDirs = options.excludeDirs || DEFAULT_EXCLUDED_DIRS; const files = await findFilesRecursive(rootDir, { extensions, excludeDirs }); // Apply additional filtering let filtered = filterFiles(files, { includePaths: options.includePaths, excludePaths: options.excludePaths }); // Sort for consistent output return filtered.sort(); } /** * Find TypeScript/TSX files */ export async function findTypeScriptFiles(rootDir = process.cwd(), options = {}) { return findFiles(rootDir, { ...options, extensions: TYPESCRIPT_EXTENSIONS }); } /** * Find JavaScript/JSX files */ export async function findJavaScriptFiles(rootDir = process.cwd(), options = {}) { return findFiles(rootDir, { ...options, extensions: JAVASCRIPT_EXTENSIONS }); } /** * Find JSON files */ export async function findJsonFiles(rootDir = process.cwd(), options = {}) { return findFiles(rootDir, { ...options, extensions: JSON_EXTENSIONS }); } /** * Find files by pattern (e.g., "*.test.ts", "*.spec.tsx") */ export async function findFilesByPattern(rootDir = process.cwd(), pattern, options = {}) { const extensions = options.extensions || ALL_EXTENSIONS; const excludeDirs = options.excludeDirs || DEFAULT_EXCLUDED_DIRS; // Convert string pattern to RegExp if needed const regex = typeof pattern === 'string' ? new RegExp(pattern.replace(/\*/g, '.*')) : pattern; const files = await findFilesRecursive(rootDir, { extensions, excludeDirs, pattern: regex }); // Apply additional filtering let filtered = filterFiles(files, { includePaths: options.includePaths, excludePaths: options.excludePaths }); return filtered.sort(); } /** * Filter files by include/exclude patterns */ export function filterFiles(files, options = {}) { let filtered = [...files]; // Apply include patterns if (options.includePaths && options.includePaths.length > 0) { filtered = filtered.filter(file => { return options.includePaths.some(pattern => { // Convert glob patterns to regex const regex = globToRegex(pattern); return regex.test(file); }); }); } // Apply exclude patterns if (options.excludePaths && options.excludePaths.length > 0) { filtered = filtered.filter(file => { return !options.excludePaths.some(pattern => { // Convert glob patterns to regex const regex = globToRegex(pattern); return regex.test(file); }); }); } return filtered; } /** * Convert simple glob pattern to regex */ function globToRegex(pattern) { // Escape special regex characters except * and ? let regex = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); // Convert glob wildcards to regex regex = regex.replace(/\*/g, '.*').replace(/\?/g, '.'); return new RegExp(`^${regex}$`); } /** * Get file statistics */ export async function getFileStats(filePath) { const stats = await fs.stat(filePath); // Count lines for text files let lines; try { const content = await fs.readFile(filePath, 'utf-8'); lines = content.split('\n').length; } catch { // Ignore errors reading file content } return { size: stats.size, modified: stats.mtime, lines }; } /** * Check if a file exists and is readable */ export async function isReadableFile(filePath) { try { await fs.access(filePath, fs.constants.R_OK); const stats = await fs.stat(filePath); return stats.isFile(); } catch { return false; } } /** * Alias for findFiles to match expected import */ export const discoverFiles = findFiles; //# sourceMappingURL=fileDiscovery.js.map