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
JavaScript
/**
* @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
};