@mondaydotcomorg/atp-compiler
Version:
Production-ready compiler for transforming async iteration patterns into resumable operations with checkpoint-based state management
141 lines • 5.72 kB
JavaScript
import { parse } from '@babel/parser';
import _traverse from '@babel/traverse';
// @ts-ignore - CommonJS/ESM compatibility
const traverse = typeof _traverse.default === 'function' ? _traverse.default : _traverse;
import * as t from '@babel/types';
import { containsAwait, isArrayMethod, isPausableCallExpression } from './utils.js';
export class AsyncIterationDetector {
detect(code) {
const patterns = [];
let batchableParallel = false;
try {
const ast = parse(code, {
sourceType: 'module',
plugins: ['typescript'],
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true,
});
traverse(ast, {
ForOfStatement: (path) => {
if (containsAwait(path.node.body)) {
patterns.push('for-of-await');
}
},
WhileStatement: (path) => {
if (containsAwait(path.node.body)) {
patterns.push('while-await');
}
},
CallExpression: (path) => {
const node = path.node;
if (isArrayMethod(node, 'map')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('map-async');
}
}
if (isArrayMethod(node, 'forEach')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('forEach-async');
}
}
if (isArrayMethod(node, 'filter')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('filter-async');
}
}
if (isArrayMethod(node, 'reduce')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('reduce-async');
}
}
if (isArrayMethod(node, 'find')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('find-async');
}
}
if (isArrayMethod(node, 'some')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('some-async');
}
}
if (isArrayMethod(node, 'every')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('every-async');
}
}
if (isArrayMethod(node, 'flatMap')) {
const callback = node.arguments[0];
if (callback && t.isFunction(callback) && callback.async) {
patterns.push('flatMap-async');
}
}
if (this.isPromiseAll(node)) {
patterns.push('promise-all');
if (this.canBatchPromiseAll(node)) {
batchableParallel = true;
}
}
if (this.isPromiseAllSettled(node)) {
patterns.push('promise-allSettled');
}
},
});
return {
needsTransform: patterns.length > 0,
patterns: [...new Set(patterns)],
batchableParallel,
};
}
catch (error) {
return {
needsTransform: false,
patterns: [],
batchableParallel: false,
};
}
}
isPromiseAll(node) {
const callee = node.callee;
return (t.isMemberExpression(callee) &&
t.isIdentifier(callee.object, { name: 'Promise' }) &&
t.isIdentifier(callee.property, { name: 'all' }));
}
isPromiseAllSettled(node) {
const callee = node.callee;
return (t.isMemberExpression(callee) &&
t.isIdentifier(callee.object, { name: 'Promise' }) &&
t.isIdentifier(callee.property, { name: 'allSettled' }));
}
canBatchPromiseAll(node) {
const arrayArg = node.arguments[0];
if (!t.isArrayExpression(arrayArg)) {
return false;
}
if (arrayArg.elements.length === 0) {
return false;
}
return arrayArg.elements.every((el) => {
if (!el || t.isSpreadElement(el)) {
return false;
}
return this.isDirectPausableCall(el);
});
}
isDirectPausableCall(node) {
if (t.isAwaitExpression(node)) {
node = node.argument;
}
if (!t.isCallExpression(node)) {
return false;
}
return isPausableCallExpression(node);
}
}
//# sourceMappingURL=detector.js.map