UNPKG

arela

Version:

AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.

308 lines 10.5 kB
/** * Static Analyzer - Parses TypeScript/JavaScript files using AST */ import { Project, SyntaxKind } from "ts-morph"; import { determineFileType, getLineCount } from "./file-scanner.js"; let project = null; /** * Initialize the ts-morph project (lazy initialization) */ function getProject() { if (!project) { project = new Project({ compilerOptions: { target: 99, // Latest module: 99, jsx: 1, // React } }); } return project; } /** * Analyze a single file */ export async function analyzeFile(filePath, fileType) { const project = getProject(); try { const sourceFile = project.addSourceFileAtPath(filePath); const content = sourceFile.getFullText(); const lines = getLineCount(filePath); const type = fileType || determineFileType(filePath, content); return { filePath, type, lines, imports: extractImports(sourceFile), exports: extractExports(sourceFile), functions: extractFunctions(sourceFile), apiEndpoints: extractApiEndpoints(sourceFile), apiCalls: extractApiCalls(sourceFile), }; } catch (error) { console.error(`Failed to analyze file ${filePath}:`, error); return { filePath, type: fileType || 'other', lines: getLineCount(filePath), imports: [], exports: [], functions: [], apiEndpoints: [], apiCalls: [], }; } } /** * Extract import declarations */ function extractImports(sourceFile) { const imports = []; try { const importDeclarations = sourceFile.getImportDeclarations(); for (const imp of importDeclarations) { const moduleSpecifier = imp.getModuleSpecifierValue(); const line = imp.getStartLineNumber(); // Extract imported names const names = []; // Default import const defaultImport = imp.getDefaultImport(); if (defaultImport) { names.push(defaultImport.getText()); } // Named imports const namedImports = imp.getNamedImports(); for (const namedImport of namedImports) { names.push(namedImport.getName()); } // Namespace import const namespaceImport = imp.getNamespaceImport(); if (namespaceImport) { names.push(`* as ${namespaceImport.getName()}`); } let importType = 'named'; if (defaultImport && namedImports.length === 0 && !namespaceImport) { importType = 'default'; } else if (namespaceImport && !defaultImport) { importType = 'namespace'; } imports.push({ from: moduleSpecifier, names: names.length > 0 ? names : [], type: importType, line, }); } } catch (error) { // Silently ignore parse errors } return imports; } /** * Extract export declarations */ function extractExports(sourceFile) { const exports = []; try { // Export declarations const exportDeclarations = sourceFile.getExportDeclarations(); for (const exp of exportDeclarations) { const moduleSpecifier = exp.getModuleSpecifierValue(); const line = exp.getStartLineNumber(); if (moduleSpecifier) { // Re-export exports.push({ name: `re-export from ${moduleSpecifier}`, type: 'named', line, }); } } // Exported functions const functions = sourceFile.getFunctions(); for (const fn of functions) { if (fn.isExported()) { exports.push({ name: fn.getName() || 'anonymous', type: 'named', line: fn.getStartLineNumber(), }); } } // Exported classes const classes = sourceFile.getClasses(); for (const cls of classes) { if (cls.isExported()) { exports.push({ name: cls.getName() || 'anonymous', type: 'named', line: cls.getStartLineNumber(), }); } } // Exported variables const variableDeclarations = sourceFile.getVariableDeclarations(); for (const varDecl of variableDeclarations) { const parent = varDecl.getParent(); if (parent && parent.getKindName && parent.getKindName() === 'VariableDeclarationList') { const parentParent = parent.getParent(); if (parentParent && parentParent.isKind && parentParent.isKind(SyntaxKind.VariableStatement)) { if (parentParent.isExported && parentParent.isExported()) { exports.push({ name: varDecl.getName(), type: 'named', line: varDecl.getStartLineNumber(), }); } } } } } catch (error) { // Silently ignore parse errors } return exports; } /** * Extract function definitions */ function extractFunctions(sourceFile) { const functions = []; try { const allFunctions = sourceFile.getFunctions(); for (const fn of allFunctions) { functions.push({ name: fn.getName() || 'anonymous', isExported: fn.isExported(), lineStart: fn.getStartLineNumber(), lineEnd: fn.getEndLineNumber(), }); } // Also get arrow functions and function expressions from variable declarations const variableDeclarations = sourceFile.getVariableDeclarations(); for (const varDecl of variableDeclarations) { const initializer = varDecl.getInitializer(); if (initializer) { const kind = initializer.getKind(); // Arrow function or function expression if (kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.FunctionExpression) { const parent = varDecl.getParent(); const isExported = parent && parent.isExported && parent.isExported(); functions.push({ name: varDecl.getName(), isExported: !!isExported, lineStart: varDecl.getStartLineNumber(), lineEnd: varDecl.getEndLineNumber(), }); } } } } catch (error) { // Silently ignore parse errors } return functions; } /** * Extract API endpoints (Express, REST framework patterns) */ function extractApiEndpoints(sourceFile) { const endpoints = []; try { const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression); for (const call of callExpressions) { const expression = call.getExpression().getText(); // Look for Express/Fastify patterns: app.get(), router.post(), etc. const match = expression.match(/^(app|router|server)\.(get|post|put|delete|patch|options|head)\s*$/i); if (match) { const method = match[2].toUpperCase(); const args = call.getArguments(); if (args.length >= 2) { // First argument is the path const pathArg = args[0].getText().replace(/["'`]/g, ''); endpoints.push({ method, path: pathArg, fileId: 0, // Will be set later line: call.getStartLineNumber(), }); } } } } catch (error) { // Silently ignore parse errors } return endpoints; } /** * Extract API calls (fetch, axios, etc.) */ function extractApiCalls(sourceFile) { const calls = []; try { const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression); for (const call of callExpressions) { const expression = call.getExpression().getText(); const args = call.getArguments(); // fetch() calls if (expression === 'fetch' && args.length > 0) { const urlArg = args[0].getText().replace(/["'`]/g, '').trim(); // Try to extract method from second argument (RequestInit) let method = 'GET'; if (args.length > 1) { const config = args[1].getText(); const methodMatch = config.match(/method\s*:\s*['"]([A-Z]+)['"]/); if (methodMatch) { method = methodMatch[1]; } } calls.push({ method, url: urlArg, line: call.getStartLineNumber(), }); } // axios calls if (expression.includes('axios') && args.length > 0) { const method = extractAxiosMethod(expression); const urlArg = args[0].getText().replace(/["'`]/g, '').trim(); calls.push({ method, url: urlArg, line: call.getStartLineNumber(), }); } } } catch (error) { // Silently ignore parse errors } return calls; } /** * Extract HTTP method from axios expression */ function extractAxiosMethod(expression) { const match = expression.match(/\.(get|post|put|delete|patch|head|options)\s*$/i); if (match) { return match[1].toUpperCase(); } return 'GET'; // Default to GET } /** * Clean up resources */ export function closeProject() { if (project) { project.getSourceFiles().forEach(file => { project.removeSourceFile(file); }); project = null; } } //# sourceMappingURL=static-analyzer.js.map