sicua
Version:
A tool for analyzing project structure and dependencies
325 lines (324 loc) • 11.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsyncDetector = void 0;
const typescript_1 = __importDefault(require("typescript"));
/**
* Utility class for detecting async patterns in functions
*/
class AsyncDetector {
constructor(typeChecker) {
this.typeChecker = typeChecker;
}
/**
* Determines if a function is async (for backward compatibility)
* @param node The function-like declaration
* @returns Boolean indicating if the function is async
*/
isAsync(node) {
return this.analyzeAsync(node).isAsync;
}
/**
* Performs detailed async analysis
* @param node The function-like declaration
* @returns Detailed async analysis result
*/
analyzeAsync(node) {
const analysis = {
isAsync: false,
hasExplicitAsync: false,
returnsPromise: false,
hasAwaitExpressions: false,
hasPromiseCreation: false,
hasPromiseChaining: false,
hasThenCatch: false,
asyncPatterns: [],
awaitCount: 0,
promiseMethodsUsed: [],
};
try {
// Check for explicit async modifier
analysis.hasExplicitAsync = this.hasAsyncModifier(node);
// Check return type for Promise
analysis.returnsPromise = this.returnsPromiseType(node);
// Analyze function body for async patterns
if (node.body) {
this.analyzeBodyForAsyncPatterns(node.body, analysis);
}
// Determine overall async status
analysis.isAsync =
analysis.hasExplicitAsync ||
analysis.returnsPromise ||
analysis.hasAwaitExpressions ||
analysis.hasPromiseCreation ||
analysis.hasPromiseChaining;
return analysis;
}
catch (error) {
// Return safe default on error
return analysis;
}
}
/**
* Checks if function has explicit async modifier
*/
hasAsyncModifier(node) {
return !!node.modifiers?.some((modifier) => modifier.kind === typescript_1.default.SyntaxKind.AsyncKeyword);
}
/**
* Checks if function returns a Promise type
*/
returnsPromiseType(node) {
try {
// Check explicit return type annotation
if (node.type) {
const typeText = node.type.getText().toLowerCase();
return this.isPromiseTypeString(typeText);
}
// Use type checker if available for inferred types
if (this.typeChecker) {
const signature = this.typeChecker.getSignatureFromDeclaration(node);
if (signature) {
const returnType = this.typeChecker.getReturnTypeOfSignature(signature);
const typeString = this.typeChecker
.typeToString(returnType)
.toLowerCase();
return this.isPromiseTypeString(typeString);
}
}
// Fallback: analyze return statements
return this.hasPromiseReturns(node);
}
catch (error) {
return false;
}
}
/**
* Analyzes function body for async patterns
*/
analyzeBodyForAsyncPatterns(body, analysis) {
const visit = (node) => {
// Check for await expressions
if (typescript_1.default.isAwaitExpression(node)) {
analysis.hasAwaitExpressions = true;
analysis.awaitCount++;
analysis.asyncPatterns.push("await expression");
}
// Check for Promise constructor calls
if (typescript_1.default.isNewExpression(node) && this.isPromiseConstructor(node)) {
analysis.hasPromiseCreation = true;
analysis.asyncPatterns.push("Promise constructor");
}
// Check for Promise static methods
if (typescript_1.default.isCallExpression(node)) {
const promiseMethod = this.getPromiseStaticMethod(node);
if (promiseMethod) {
analysis.hasPromiseCreation = true;
analysis.promiseMethodsUsed.push(promiseMethod);
analysis.asyncPatterns.push(`Promise.${promiseMethod}`);
}
// Check for .then/.catch/.finally chaining
const chainMethod = this.getPromiseChainMethod(node);
if (chainMethod) {
analysis.hasPromiseChaining = true;
analysis.hasThenCatch =
chainMethod === "then" || chainMethod === "catch";
analysis.promiseMethodsUsed.push(chainMethod);
analysis.asyncPatterns.push(`.${chainMethod}() chaining`);
}
// Check for common async library calls
const asyncLibraryCall = this.getAsyncLibraryCall(node);
if (asyncLibraryCall) {
analysis.asyncPatterns.push(asyncLibraryCall);
}
}
typescript_1.default.forEachChild(node, visit);
};
visit(body);
}
/**
* Checks if a type string represents a Promise
*/
isPromiseTypeString(typeString) {
const cleanType = typeString.toLowerCase().trim();
return (cleanType.includes("promise<") ||
cleanType.startsWith("promise") ||
cleanType.includes("thenable<") ||
cleanType.includes("awaitable<") ||
cleanType.includes("promiselike<"));
}
/**
* Checks if function has return statements that return Promises
*/
hasPromiseReturns(node) {
if (!node.body) {
return false;
}
// Handle arrow function expression body
if (typescript_1.default.isArrowFunction(node) && !typescript_1.default.isBlock(node.body)) {
return this.isPromiseExpression(node.body);
}
// Handle block body
if (typescript_1.default.isBlock(node.body)) {
let hasPromiseReturn = false;
const visit = (node) => {
if (typescript_1.default.isReturnStatement(node) && node.expression) {
if (this.isPromiseExpression(node.expression)) {
hasPromiseReturn = true;
}
}
typescript_1.default.forEachChild(node, visit);
};
visit(node.body);
return hasPromiseReturn;
}
return false;
}
/**
* Checks if an expression creates or returns a Promise
*/
isPromiseExpression(expression) {
// New Promise()
if (typescript_1.default.isNewExpression(expression) &&
this.isPromiseConstructor(expression)) {
return true;
}
// Promise.resolve(), Promise.reject(), etc.
if (typescript_1.default.isCallExpression(expression) &&
this.getPromiseStaticMethod(expression)) {
return true;
}
// .then/.catch chaining
if (typescript_1.default.isCallExpression(expression) &&
this.getPromiseChainMethod(expression)) {
return true;
}
// fetch(), axios(), etc.
if (typescript_1.default.isCallExpression(expression) &&
this.getAsyncLibraryCall(expression)) {
return true;
}
return false;
}
/**
* Checks if a call expression is a Promise constructor
*/
isPromiseConstructor(node) {
if (typescript_1.default.isIdentifier(node.expression)) {
return node.expression.text === "Promise";
}
return false;
}
/**
* Gets Promise static method name if the call is to Promise static method
*/
getPromiseStaticMethod(node) {
if (typescript_1.default.isPropertyAccessExpression(node.expression)) {
const object = node.expression.expression;
const method = node.expression.name;
if (typescript_1.default.isIdentifier(object) &&
object.text === "Promise" &&
typescript_1.default.isIdentifier(method)) {
const methodName = method.text;
const staticMethods = [
"resolve",
"reject",
"all",
"allSettled",
"race",
"any",
];
return staticMethods.includes(methodName) ? methodName : null;
}
}
return null;
}
/**
* Gets Promise chain method name if the call is promise chaining
*/
getPromiseChainMethod(node) {
if (typescript_1.default.isPropertyAccessExpression(node.expression)) {
const method = node.expression.name;
if (typescript_1.default.isIdentifier(method)) {
const methodName = method.text;
const chainMethods = ["then", "catch", "finally"];
return chainMethods.includes(methodName) ? methodName : null;
}
}
return null;
}
/**
* Identifies common async library calls
*/
getAsyncLibraryCall(node) {
const callText = node.expression.getText().toLowerCase();
// Common async patterns
const asyncPatterns = [
{ pattern: "fetch", name: "fetch API" },
{ pattern: "axios", name: "axios request" },
{ pattern: "request", name: "HTTP request" },
{ pattern: "settimeout", name: "setTimeout" },
{ pattern: "setinterval", name: "setInterval" },
{ pattern: "nexttick", name: "process.nextTick" },
{ pattern: "setimmediate", name: "setImmediate" },
{ pattern: "requestanimationframe", name: "requestAnimationFrame" },
{ pattern: "queryselector", name: "DOM query" },
];
for (const { pattern, name } of asyncPatterns) {
if (callText.includes(pattern)) {
return name;
}
}
return null;
}
/**
* Checks if function uses async/await pattern
*/
hasAsyncAwaitPattern(node) {
return this.hasAsyncModifier(node) && this.hasAwaitInBody(node);
}
/**
* Checks if function uses Promise pattern (then/catch)
*/
hasPromisePattern(node) {
const analysis = this.analyzeAsync(node);
return analysis.hasPromiseChaining && analysis.hasThenCatch;
}
/**
* Checks if function body contains await expressions
*/
hasAwaitInBody(node) {
if (!node.body) {
return false;
}
let hasAwait = false;
const visit = (node) => {
if (typescript_1.default.isAwaitExpression(node)) {
hasAwait = true;
return;
}
typescript_1.default.forEachChild(node, visit);
};
visit(node.body);
return hasAwait;
}
/**
* Gets async complexity score
*/
getAsyncComplexity(node) {
const analysis = this.analyzeAsync(node);
let complexity = 0;
if (analysis.hasExplicitAsync)
complexity += 1;
if (analysis.hasAwaitExpressions)
complexity += analysis.awaitCount * 0.5;
if (analysis.hasPromiseCreation)
complexity += 1;
if (analysis.hasPromiseChaining)
complexity += 1;
return Math.round(complexity * 10) / 10;
}
}
exports.AsyncDetector = AsyncDetector;