UNPKG

gitlify

Version:

A powerful CLI tool to analyze uncommitted git changes with detailed reports, function detection, and beautiful terminal output

180 lines (147 loc) 5.64 kB
const fs = require('fs'); const path = require('path'); const InputValidator = require('./utils/validator.js'); const { FileSystemError, ValidationError } = require('./utils/error-handler.js'); const config = require('./config.js'); class CodeAnalyzer { constructor() { this.supportedExtensions = config.supportedExtensions; } async analyzeFiles(files) { // Limit concurrent operations const config = require('./config.js'); const ProgressReporter = require('./utils/progress.js'); const concurrentLimit = config.concurrentFileLimit; const analyzedFiles = []; const progress = new ProgressReporter(files.length, { showBar: true, showPercentage: true, showSpeed: true, showETA: true }); // Process files in batches to avoid overwhelming the system for (let i = 0; i < files.length; i += concurrentLimit) { const batch = files.slice(i, i + concurrentLimit); const promises = batch.map(file => this.analyzeFile(file)); const batchResults = await Promise.all(promises); analyzedFiles.push(...batchResults); progress.update(batch.length, `Analyzed ${analyzedFiles.length}/${files.length} files`); } progress.complete(`Analysis complete - ${analyzedFiles.length} files processed`); return analyzedFiles; } async analyzeFile(file) { const filePath = file.filePath; const extension = path.extname(filePath).toLowerCase(); // Only analyze supported file types if (!this.supportedExtensions.includes(extension)) { return { ...file, functions: [], changedFunctions: [] }; } try { // Path validation and resolution const fullPath = InputValidator.validateAndResolvePath(filePath, process.cwd()); // File size validation InputValidator.validateFileSize(fullPath, config.maxFileSize); // Read current file content const currentContent = fs.readFileSync(fullPath, 'utf8'); const functions = this.extractFunctions(currentContent, extension); // Find which functions contain changes const changedFunctions = this.findChangedFunctions(file.changedLines, functions); return { ...file, functions, changedFunctions }; } catch (error) { // If file can't be read, return without function analysis return { ...file, functions: [], changedFunctions: [] }; } } extractFunctions(content, extension) { const functions = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Function patterns for different file types const patterns = this.getFunctionPatterns(extension); for (const pattern of patterns) { const match = line.match(pattern.regex); if (match) { functions.push({ name: match[1] || match[2] || 'anonymous', startLine: i + 1, endLine: this.findFunctionEnd(lines, i), type: pattern.type }); break; } } } return functions; } getFunctionPatterns(extension) { const patterns = [ // Function declarations { regex: /^function\s+(\w+)\s*\(/, type: 'function' }, { regex: /^const\s+(\w+)\s*=\s*function\s*\(/, type: 'const-function' }, { regex: /^let\s+(\w+)\s*=\s*function\s*\(/, type: 'let-function' }, { regex: /^var\s+(\w+)\s*=\s*function\s*\(/, type: 'var-function' }, // Arrow functions { regex: /^const\s+(\w+)\s*=\s*\([^)]*\)\s*=>/, type: 'arrow-function' }, { regex: /^let\s+(\w+)\s*=\s*\([^)]*\)\s*=>/, type: 'arrow-function' }, { regex: /^var\s+(\w+)\s*=\s*\([^)]*\)\s*=>/, type: 'arrow-function' }, // Method definitions (for classes) { regex: /^\s*(\w+)\s*\([^)]*\)\s*\{/, type: 'method' }, { regex: /^\s*(\w+)\s*\([^)]*\)\s*=>/, type: 'arrow-method' }, // Class methods { regex: /^\s*(\w+)\s*\([^)]*\)\s*\{/, type: 'class-method' }, // Async functions { regex: /^async\s+function\s+(\w+)\s*\(/, type: 'async-function' }, { regex: /^const\s+(\w+)\s*=\s*async\s*\([^)]*\)\s*=>/, type: 'async-arrow' }, { regex: /^\s*async\s+(\w+)\s*\([^)]*\)\s*\{/, type: 'async-method' } ]; return patterns; } findFunctionEnd(lines, startIndex) { let braceCount = 0; let inFunction = false; for (let i = startIndex; i < lines.length; i++) { const line = lines[i]; // Count opening braces for (const char of line) { if (char === '{') { braceCount++; inFunction = true; } else if (char === '}') { braceCount--; if (inFunction && braceCount === 0) { return i + 1; } } } } return lines.length; } findChangedFunctions(changedLines, functions) { const changedFunctions = []; for (const func of functions) { // Check if any changed line is within this function const hasChanges = changedLines.some(line => line.lineNumber >= func.startLine && line.lineNumber <= func.endLine ); if (hasChanges) { changedFunctions.push(func); } } return changedFunctions; } } module.exports = CodeAnalyzer;