UNPKG

scai

Version:

> AI-powered CLI tool for commit messages **and** pull request reviews — using local models.

123 lines (122 loc) 5.08 kB
import { parse } from 'acorn'; import { ancestor as walkAncestor } from 'acorn-walk'; import { generateEmbedding } from '../../lib/generateEmbedding.js'; import path from 'path'; import { log } from '../../utils/log.js'; import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate } from '../sqlTemplates.js'; import { getDbForRepo } from '../client.js'; function getFunctionName(node, parent, fileName) { if (node.id?.name) return node.id.name; if (parent?.type === 'VariableDeclarator' && parent.id?.name) return parent.id.name; if (parent?.type === 'Property' && parent.key?.name) return parent.key.name; if (parent?.type === 'AssignmentExpression' && parent.left?.name) return parent.left.name; if (parent?.type === 'MethodDefinition' && parent.key?.name) return parent.key.name; return `${fileName}:<anon>`; } export async function extractFromJS(filePath, content, fileId) { const db = getDbForRepo(); try { console.log(`[Debug] Attempting to parse: ${filePath}`); console.log(`[Debug] First 3 lines:\n${content.split('\n').slice(0, 3).join('\n')}`); const ast = parse(content, { ecmaVersion: 'latest', sourceType: 'module', locations: true, }); const functions = []; walkAncestor(ast, { FunctionDeclaration(node, ancestors) { const parent = ancestors[ancestors.length - 2]; const name = getFunctionName(node, parent, path.basename(filePath)); functions.push({ name, start_line: node.loc?.start.line ?? -1, end_line: node.loc?.end.line ?? -1, content: content.slice(node.start, node.end), }); }, FunctionExpression(node, ancestors) { const parent = ancestors[ancestors.length - 2]; const name = getFunctionName(node, parent, path.basename(filePath)); functions.push({ name, start_line: node.loc?.start.line ?? -1, end_line: node.loc?.end.line ?? -1, content: content.slice(node.start, node.end), }); }, ArrowFunctionExpression(node, ancestors) { const parent = ancestors[ancestors.length - 2]; const name = getFunctionName(node, parent, path.basename(filePath)); functions.push({ name, start_line: node.loc?.start.line ?? -1, end_line: node.loc?.end.line ?? -1, content: content.slice(node.start, node.end), }); }, }); if (functions.length === 0) { log(`⚠️ No functions found in: ${filePath}`); db.prepare(markFileAsSkippedTemplate).run({ id: fileId }); return false; } log(`🔍 Found ${functions.length} functions in ${filePath}`); for (const fn of functions) { const embedding = await generateEmbedding(fn.content); const result = db.prepare(` INSERT INTO functions ( file_id, name, start_line, end_line, content, embedding, lang ) VALUES ( @file_id, @name, @start_line, @end_line, @content, @embedding, @lang ) `).run({ file_id: fileId, name: fn.name, start_line: fn.start_line, end_line: fn.end_line, content: fn.content, embedding: JSON.stringify(embedding), lang: 'js' }); const callerId = result.lastInsertRowid; const fnAst = parse(fn.content, { ecmaVersion: 'latest', sourceType: 'module', locations: true, }); const calls = []; walkAncestor(fnAst, { CallExpression(node) { if (node.callee?.type === 'Identifier' && node.callee.name) { calls.push({ calleeName: node.callee.name }); } } }); for (const call of calls) { db.prepare(` INSERT INTO function_calls (caller_id, callee_name) VALUES (@caller_id, @callee_name) `).run({ caller_id: callerId, callee_name: call.calleeName }); } log(`📌 Indexed function: ${fn.name} with ${calls.length} calls`); } db.prepare(markFileAsExtractedTemplate).run({ id: fileId }); log(`✅ Marked functions as extracted for ${filePath}`); return true; } catch (err) { log(`❌ Failed to extract from: ${filePath}`); log(` ↳ ${String(err.message)}`); db.prepare(markFileAsFailedTemplate).run({ id: fileId }); return false; } }