@gati-framework/cli
Version:
CLI tool for Gati framework - create, develop, build and deploy cloud-native applications
123 lines • 3.98 kB
JavaScript
/**
* @module cli/analyzer/hook-extractor
* @description Extracts hook definitions from handler TypeScript code
*
* Implements Task 21: Hook Manifest Recording
* - Detects lctx.before(), lctx.after(), lctx.catch() calls
* - Extracts hook metadata (id, level, async, timeout, retries)
* - Captures source location for debugging
*/
import * as ts from 'typescript';
/**
* Extract all hooks from a TypeScript source file
*/
export function extractHooks(sourceFile) {
const hooks = [];
function visit(node) {
// Look for lctx.before(), lctx.after(), lctx.catch() calls
if (ts.isCallExpression(node)) {
const expr = node.expression;
if (ts.isPropertyAccessExpression(expr)) {
const obj = expr.expression;
const method = expr.name.text;
// Check if it's lctx.before/after/catch
if (ts.isIdentifier(obj) &&
obj.text === 'lctx' &&
['before', 'after', 'catch'].includes(method)) {
const hook = extractHookFromCall(node, method, sourceFile);
if (hook) {
hooks.push(hook);
}
}
}
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return hooks;
}
/**
* Extract hook details from a call expression
*/
function extractHookFromCall(node, type, sourceFile) {
const args = node.arguments;
if (args.length === 0) {
return null;
}
const hookConfig = args[0];
return {
id: extractStringLiteral(hookConfig, 'id') || `${type}_${Date.now()}`,
type,
level: extractStringLiteral(hookConfig, 'level') || 'handler',
isAsync: isAsyncFunction(hookConfig),
timeout: extractNumberLiteral(hookConfig, 'timeout'),
retries: extractNumberLiteral(hookConfig, 'retries'),
sourceLocation: getSourceLocation(node, sourceFile),
};
}
/**
* Extract string literal from object property
*/
function extractStringLiteral(node, propertyName) {
if (!ts.isObjectLiteralExpression(node)) {
return undefined;
}
for (const prop of node.properties) {
if (ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === propertyName &&
ts.isStringLiteral(prop.initializer)) {
return prop.initializer.text;
}
}
return undefined;
}
/**
* Extract number literal from object property
*/
function extractNumberLiteral(node, propertyName) {
if (!ts.isObjectLiteralExpression(node)) {
return undefined;
}
for (const prop of node.properties) {
if (ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === propertyName &&
ts.isNumericLiteral(prop.initializer)) {
return parseInt(prop.initializer.text, 10);
}
}
return undefined;
}
/**
* Check if hook function is async
*/
function isAsyncFunction(node) {
if (!ts.isObjectLiteralExpression(node)) {
return false;
}
for (const prop of node.properties) {
if (ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'fn') {
const fn = prop.initializer;
// Check for async keyword
if (ts.isFunctionExpression(fn) || ts.isArrowFunction(fn)) {
return !!(fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword));
}
}
}
return false;
}
/**
* Get source location for a node
*/
function getSourceLocation(node, sourceFile) {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
return {
file: sourceFile.fileName,
line: line + 1, // Convert to 1-based
column: character + 1, // Convert to 1-based
};
}
//# sourceMappingURL=hook-extractor.js.map