agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
149 lines (129 loc) • 4.7 kB
JavaScript
/**
* @file AST-based promise in loop detector
* @description Detects promise creation in loops using AST analysis
*/
const { parseToAST, isInsideLoop, getNodeLine, getNodeSource, walk } = require('./astParser');
const { PERFORMANCE_PATTERNS } = require('../performancePatterns');
/**
* Detect promise creation in loops using AST
* @param {string} content - File content
* @param {string} filePath - File path
* @returns {Array} Array of promise in loop issues
*/
function detectPromiseInLoopsAST(content, filePath) {
const issues = [];
const ast = parseToAST(content, filePath);
if (!ast) return [];
const pattern = PERFORMANCE_PATTERNS['promise_in_loop'];
// Track if we're already using Promise.all or similar
const promiseAllUsage = new Set();
walk.simple(ast, {
CallExpression(node) {
// Check for Promise.all, Promise.allSettled, etc.
if (node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'Promise' &&
['all', 'allSettled', 'race'].includes(node.callee.property.name)) {
// Mark this location as using Promise.all
promiseAllUsage.add(getNodeLine(node));
}
}
});
// Find promise creation patterns
walk.simple(ast, {
NewExpression(node) {
// new Promise(...)
if (node.callee.name === 'Promise') {
checkPromiseInLoop(node);
}
},
CallExpression(node) {
// Promise.resolve/reject
if (node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'Promise' &&
['resolve', 'reject'].includes(node.callee.property.name)) {
checkPromiseInLoop(node);
}
// fetch() calls
if (node.callee.name === 'fetch') {
checkPromiseInLoop(node);
}
// axios calls
if (node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'axios' &&
['get', 'post', 'put', 'delete', 'patch', 'request'].includes(node.callee.property.name)) {
checkPromiseInLoop(node);
}
// .then() chains
if (node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'then') {
checkPromiseInLoop(node);
}
}
});
function checkPromiseInLoop(node) {
const loop = isInsideLoop(ast, node);
if (!loop) return;
const line = getNodeLine(node);
// Check if this line is near a Promise.all usage
const hasPromiseAll = Array.from(promiseAllUsage).some(
promiseAllLine => Math.abs(promiseAllLine - line) <= 5
);
if (!hasPromiseAll) {
const loopType = getLoopType(loop);
const promiseType = getPromiseType(node);
issues.push({
type: 'promise_in_loop',
severity: pattern.severity,
category: pattern.category,
location: `${filePath}:${line}`,
line: line,
code: getNodeSource(content, node).trim(),
loopType: loopType,
promiseType: promiseType,
description: `Creating ${promiseType} in ${loopType} can cause memory and performance issues`,
summary: `${promiseType} creation inside ${loopType}`,
recommendation: 'Collect promises in array and use Promise.all() for concurrent execution',
effort: pattern.effort,
impact: pattern.impact,
estimatedSavings: '30-50% memory and performance improvement'
});
}
}
function getLoopType(loopNode) {
if (loopNode.type === 'ForStatement') return 'for loop';
if (loopNode.type === 'WhileStatement') return 'while loop';
if (loopNode.type === 'DoWhileStatement') return 'do-while loop';
if (loopNode.type === 'ForInStatement') return 'for-in loop';
if (loopNode.type === 'ForOfStatement') return 'for-of loop';
if (loopNode.type === 'CallExpression') {
return `${loopNode.callee.property.name} loop`;
}
return 'loop';
}
function getPromiseType(node) {
if (node.type === 'NewExpression' && node.callee.name === 'Promise') {
return 'new Promise';
}
if (node.type === 'CallExpression') {
if (node.callee.type === 'MemberExpression') {
if (node.callee.object.name === 'Promise') {
return `Promise.${node.callee.property.name}`;
}
if (node.callee.object.name === 'axios') {
return `axios.${node.callee.property.name}`;
}
if (node.callee.property.name === 'then') {
return 'promise chain';
}
}
if (node.callee.name === 'fetch') {
return 'fetch (use axios instead)';
}
}
return 'promise';
}
return issues;
}
module.exports = {
detectPromiseInLoopsAST
};