UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

230 lines (207 loc) 9.53 kB
/** * @file Advanced AST-based performance analyzer with intelligent fallback strategy * @description Single responsibility: Orchestrate comprehensive file-level performance analysis using AST and regex detection * * This analyzer represents the evolution of performance detection from regex-based patterns to * sophisticated AST-based analysis. It provides intelligent fallback to regex patterns when AST * parsing fails, ensuring robust analysis across diverse JavaScript and TypeScript codebases * with comprehensive coverage of modern performance anti-patterns. * * Design rationale: * - AST-based detection provides superior accuracy over regex pattern matching * - Intelligent fallback ensures analysis robustness when AST parsing encounters issues * - Modular detector architecture enables focused testing and maintenance of individual patterns * - Comprehensive score calculation provides quantitative performance assessment * - Multiple detector categories cover algorithmic, I/O, memory, and framework-specific issues * * Analysis architecture: * - Primary AST-based detectors for accurate code structure analysis * - Fallback regex detectors maintain compatibility across all file types * - Performance score calculation weighs issues by severity and impact * - Contextual analysis considers file type and framework patterns * - Priority scoring guides optimization effort allocation */ // Import AST-based detectors const { detectQuadraticPatternsAST } = require('./ast/astQuadraticDetector'); const { detectStringConcatInLoopsAST } = require('./ast/astStringConcatDetector'); const { detectPromiseInLoopsAST } = require('./ast/astPromiseLoopDetector'); const { detectSerialAwaitsAST } = require('./ast/astSerialAwaitDetector'); const { detectRepeatedDomQueriesAST } = require('./ast/astRepeatedDomDetector'); // Import original regex-based detectors as fallback const { detectQuadraticPatterns } = require('./quadraticPatternDetector'); const { detectSyncIO } = require('./syncIODetector'); const { detectJSONInLoops } = require('./jsonInLoopDetector'); const { detectSerialAwaits } = require('./serialAwaitDetector'); const { detectLargeInlineObjects } = require('./largeInlineObjectDetector'); const { detectUnboundedArrays } = require('./unboundedArrayDetector'); const { detectInefficientRegex } = require('./inefficientRegexDetector'); const { detectMemoryLeaks } = require('./memoryLeakDetector'); const { detectStringConcatInLoops } = require('./stringConcatLoopDetector'); const { detectRepeatedDomQueries } = require('./repeatedDomQueryDetector'); const { detectReactRenderIssues } = require('./reactRenderDetector'); const { detectPromiseInLoops } = require('./promiseInLoopDetector'); const { detectWaterfallFetch } = require('./waterfallFetchDetector'); const { analyzeMemoryComplexity } = require('./memoryComplexityAnalyzer'); const { detectRecursivePatterns } = require('./recursivePatternDetector'); const { analyzeAsyncPatterns } = require('./asyncPatternAnalyzer'); const { analyzeReactPatterns } = require('./reactPatternAnalyzer'); const { analyzeDatabasePatterns } = require('./databasePatternAnalyzer'); const { calculatePerformanceScore, getPerformanceGrade } = require('./performanceScoreCalculator'); const { calculateContextualEffort, calculateContextualImpact, calculatePriorityScore } = require('./contextualScorer'); /** * Analyzes a single file for performance issues using AST when possible * @param {string} filePath - Path to the file * @param {string} content - File content (optional) * @returns {Promise<Object>} Performance analysis results for the file */ async function analyzeFilePerformance(filePath, content = null) { const fs = require('fs'); // Enhanced filtering similar to security tool to reduce false positives const filePathLower = filePath.toLowerCase(); const baseName = filePathLower.split('/').pop(); // Skip files that typically have intentional performance examples or simple patterns const skipPatterns = [ '.test.', '/test', 'demo/', 'example/', 'tmp/', '/logs/', 'coverage/', '.md', '.txt', '.json', 'package.json', 'readme', 'changelog', 'formatter', 'analyzer', 'scanner', 'detector', 'outputformatter', 'helpformatter', 'argumentparser', 'errorhandler', '.config.', '/cli/', 'index.js', 'index.test.js', 'package-lock.json', 'jest.config.js', '.gitignore', 'replit.md', 'patterns.js' ]; const shouldSkip = skipPatterns.some(pattern => filePathLower.includes(pattern.toLowerCase()) ); if (shouldSkip) { return { file: filePath, issues: [], // Skip analysis for documentation/pattern/test files summary: { totalIssues: 0, score: 100, grade: 'A', analysisSkipped: true, skipReason: 'File type excluded from performance analysis' } }; } if (!content) { try { content = await fs.promises.readFile(filePath, 'utf8'); } catch (error) { return { file: filePath, error: `Could not read file: ${error.message}`, issues: [], summary: { totalIssues: 0, score: 100, grade: 'A' } }; } } // Determine if we can use AST parsing for this file const useAST = canUseAST(filePath); // Run all performance detectors const issues = []; // Detectors that have AST versions if (useAST) { try { // Use AST-based detectors issues.push(...detectQuadraticPatternsAST(content, filePath)); issues.push(...detectStringConcatInLoopsAST(content, filePath)); issues.push(...detectPromiseInLoopsAST(content, filePath)); issues.push(...detectSerialAwaitsAST(content, filePath)); issues.push(...detectRepeatedDomQueriesAST(content, filePath)); } catch (astError) { // Fallback to regex-based detection console.warn(`AST parsing failed for ${filePath}, using regex fallback:`, astError.message); issues.push(...detectQuadraticPatterns(content, filePath)); issues.push(...detectStringConcatInLoops(content, filePath)); issues.push(...detectPromiseInLoops(content, filePath)); issues.push(...detectSerialAwaits(content, filePath)); issues.push(...detectRepeatedDomQueries(content, filePath)); } } else { // Use regex-based detectors for non-JS files issues.push(...detectQuadraticPatterns(content, filePath)); issues.push(...detectStringConcatInLoops(content, filePath)); issues.push(...detectPromiseInLoops(content, filePath)); issues.push(...detectSerialAwaits(content, filePath)); issues.push(...detectRepeatedDomQueries(content, filePath)); } // Detectors that don't have AST versions yet (still use regex) issues.push(...detectSyncIO(content, filePath)); issues.push(...detectJSONInLoops(content, filePath)); issues.push(...detectLargeInlineObjects(content, filePath)); issues.push(...detectUnboundedArrays(content, filePath)); issues.push(...detectInefficientRegex(content, filePath)); issues.push(...detectMemoryLeaks(content, filePath)); issues.push(...detectReactRenderIssues(content, filePath)); issues.push(...detectWaterfallFetch(content, filePath)); // New advanced pattern analyzers issues.push(...analyzeMemoryComplexity(content, filePath)); issues.push(...detectRecursivePatterns(content, filePath)); issues.push(...analyzeAsyncPatterns(content, filePath)); issues.push(...analyzeReactPatterns(content, filePath)); issues.push(...analyzeDatabasePatterns(content, filePath)); // Apply contextual scoring to all issues const contextualIssues = issues.map(issue => { const contextualEffort = calculateContextualEffort(issue, content); const contextualImpact = calculateContextualImpact(issue, content); const priorityScore = calculatePriorityScore(issue, content); return { ...issue, effort: contextualEffort, originalEffort: issue.effort, impact: contextualImpact, originalImpact: issue.impact, priorityScore: priorityScore }; }); // Sort issues by priority score (highest first) contextualIssues.sort((a, b) => b.priorityScore - a.priorityScore); // Calculate metrics const score = calculatePerformanceScore(contextualIssues); const grade = getPerformanceGrade(score); // Calculate category breakdown const categoryBreakdown = {}; const severityBreakdown = { HIGH: 0, MEDIUM: 0, LOW: 0 }; let totalEffort = 0; contextualIssues.forEach(issue => { categoryBreakdown[issue.category] = (categoryBreakdown[issue.category] || 0) + 1; severityBreakdown[issue.severity] = (severityBreakdown[issue.severity] || 0) + 1; totalEffort += issue.effort || 1; }); return { file: filePath, issues: contextualIssues, performanceScore: score, metrics: { totalIssues: contextualIssues.length, score, grade, totalEffort, categoryBreakdown, severityBreakdown, astParsing: useAST, contextualScoring: true }, summary: { totalIssues: contextualIssues.length, score, grade, totalEffort, categoryBreakdown, severityBreakdown } }; } /** * Check if we can use AST parsing for this file * @param {string} filePath - File path * @returns {boolean} Whether to use AST parsing */ function canUseAST(filePath) { // Only use AST for JavaScript/TypeScript files return /\.(js|jsx|ts|tsx)$/i.test(filePath); } module.exports = { analyzeFilePerformance };