agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
150 lines (127 loc) • 4.76 kB
JavaScript
/**
* @file AST-based serial await detector
* @description Detects sequential await patterns that could be parallelized
*/
const { parseToAST, getNodeLine, getNodeSource, walk } = require('./astParser');
const { PERFORMANCE_PATTERNS } = require('../performancePatterns');
/**
* Detect serial await patterns using AST
* @param {string} content - File content
* @param {string} filePath - File path
* @returns {Array} Array of serial await issues
*/
function detectSerialAwaitsAST(content, filePath) {
const issues = [];
const ast = parseToAST(content, filePath);
if (!ast) return [];
const pattern = PERFORMANCE_PATTERNS['serial_await'];
// Find all async functions
const asyncFunctions = [];
walk.simple(ast, {
FunctionDeclaration(node) {
if (node.async) asyncFunctions.push(node);
},
FunctionExpression(node) {
if (node.async) asyncFunctions.push(node);
},
ArrowFunctionExpression(node) {
if (node.async) asyncFunctions.push(node);
}
});
// Analyze each async function for serial awaits
asyncFunctions.forEach(func => {
const awaitExpressions = [];
// Collect all await expressions in this function
walk.simple(func, {
AwaitExpression(node) {
awaitExpressions.push({
node,
line: getNodeLine(node)
});
}
});
// Look for sequential awaits that could be parallelized
for (let i = 0; i < awaitExpressions.length - 1; i++) {
const current = awaitExpressions[i];
const next = awaitExpressions[i + 1];
// Check if awaits are close together (within 5 lines)
if (next.line - current.line <= 5) {
// Check if they're independent (not using result of previous)
if (areIndependentAwaits(current.node, next.node, content)) {
// Check if we have at least 3 sequential awaits
let sequentialCount = 2;
let lastLine = next.line;
for (let j = i + 2; j < awaitExpressions.length; j++) {
if (awaitExpressions[j].line - lastLine <= 5 &&
areIndependentAwaits(awaitExpressions[j-1].node, awaitExpressions[j].node, content)) {
sequentialCount++;
lastLine = awaitExpressions[j].line;
} else {
break;
}
}
if (sequentialCount >= 3) {
issues.push({
type: 'serial_await',
severity: pattern.severity,
category: pattern.category,
location: `${filePath}:${current.line}-${lastLine}`,
line: current.line,
code: getNodeSource(content, current.node).trim(),
awaitCount: sequentialCount,
description: `${sequentialCount} sequential awaits that could be parallelized`,
summary: `Sequential awaits reducing concurrency`,
recommendation: 'Use Promise.all() for independent async operations',
effort: pattern.effort,
impact: pattern.impact,
estimatedSavings: '50-70% latency reduction for grouped ops'
});
// Skip the reported awaits
i += sequentialCount - 1;
}
}
}
}
});
return issues;
}
function areIndependentAwaits(await1, await2, content) {
// Get the full statements containing the awaits
const stmt1 = getContainingStatement(await1);
const stmt2 = getContainingStatement(await2);
if (!stmt1 || !stmt2) return false;
// If first await assigns to a variable, check if second uses that variable
if (stmt1.type === 'VariableDeclaration' && stmt1.declarations[0]) {
const varName = stmt1.declarations[0].id.name;
const stmt2Source = getNodeSource(content, stmt2);
// If the second statement uses the variable from the first, they're dependent
if (new RegExp(`\\b${varName}\\b`).test(stmt2Source)) {
return false;
}
}
// If first is assignment, check if second uses that variable
if (stmt1.type === 'ExpressionStatement' &&
stmt1.expression.type === 'AssignmentExpression') {
const varName = stmt1.expression.left.name;
const stmt2Source = getNodeSource(content, stmt2);
if (varName && new RegExp(`\\b${varName}\\b`).test(stmt2Source)) {
return false;
}
}
return true;
}
function getContainingStatement(node) {
let current = node;
while (current) {
if (current.type === 'VariableDeclaration' ||
current.type === 'ExpressionStatement' ||
current.type === 'ReturnStatement') {
return current;
}
current = current.parent;
}
return null;
}
module.exports = {
detectSerialAwaitsAST
};