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
JavaScript
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;