UNPKG

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