UNPKG

remcode

Version:

Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.

420 lines (419 loc) 16.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ContextExtractor = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const logger_1 = require("../utils/logger"); // Use require for TypeScript ESTree to avoid module resolution issues const { parse } = require('@typescript-eslint/typescript-estree'); const logger = (0, logger_1.getLogger)('ContextExtractor'); class ContextExtractor { /** * Extract code context from a file based on line range * @param filePath Path to the source file * @param startLine Starting line number (0-indexed) * @param endLine Ending line number (0-indexed) * @returns Extracted code context */ async extractContext(filePath, startLine, endLine) { logger.info(`Extracting context for ${filePath}:${startLine}-${endLine}`); try { // Read file content const content = await fs.promises.readFile(filePath, 'utf-8'); const lines = content.split('\n'); // Make sure the requested lines are within the file bounds if (startLine < 0 || endLine >= lines.length || startLine > endLine) { throw new Error(`Invalid line range: ${startLine}-${endLine} for file with ${lines.length} lines`); } // Extract the target content const targetContent = lines.slice(startLine, endLine + 1).join('\n'); // Extract surrounding context (5 lines before and after) const contextSize = 5; const surroundingStart = Math.max(0, startLine - contextSize); const surroundingEnd = Math.min(lines.length - 1, endLine + contextSize); const surroundingLines = [ ...lines.slice(surroundingStart, startLine), ...lines.slice(endLine + 1, surroundingEnd + 1) ]; // Analyze file structure to find imports, classes, and functions const fileStructure = await this.getFileStructure(filePath); // Find class context if the code is within a class let classContext; for (const classInfo of fileStructure.classes) { if (startLine >= classInfo.startLine && endLine <= classInfo.endLine) { classContext = `class ${classInfo.name}`; break; } } // Find related functions that might be relevant to the selected code const relatedFunctions = []; for (const func of fileStructure.functions) { // Include functions that overlap with the target range const isOverlapping = (func.startLine <= endLine && func.endLine >= startLine); // Or functions that are close to the target range const isClose = Math.abs(func.startLine - endLine) <= 20 || Math.abs(func.endLine - startLine) <= 20; if (isOverlapping || isClose) { relatedFunctions.push(func.name); } } // Extract all imports const imports = fileStructure.imports.map(imp => `import { ${imp.imported.join(', ')} } from "${imp.source}"`); // Get module context from the file path const moduleContext = path.basename(filePath, path.extname(filePath)); return { targetContent, surroundingLines, relatedFunctions, imports, classContext, moduleContext, fileStructure }; } catch (error) { logger.error(`Error extracting context: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Parse a file and extract its structure (classes, functions, imports, exports) * @param filePath Path to the source file * @returns Structured representation of the file */ async getFileStructure(filePath) { logger.info(`Analyzing file structure: ${filePath}`); try { const content = await fs.promises.readFile(filePath, 'utf-8'); const fileExt = path.extname(filePath).toLowerCase(); if (['.ts', '.tsx', '.js', '.jsx'].includes(fileExt)) { return this.parseTypescript(content, fileExt.includes('x')); } else { // For non-JS/TS files, provide a simplified structure return this.parseGenericFile(content); } } catch (error) { logger.error(`Error analyzing file structure: ${error instanceof Error ? error.message : String(error)}`); return { classes: [], functions: [], exports: [], imports: [] }; } } /** * Parse TypeScript/JavaScript files to extract their structure * @param content File content * @param isJsx Whether the file contains JSX * @returns Structured representation of the file */ parseTypescript(content, isJsx) { try { const ast = parse(content, { jsx: isJsx, loc: true, errorOnUnknownASTType: false }); const classes = []; const functions = []; const exports = []; const imports = []; // Process the AST to extract code structure this.traverseAst(ast, { classes, functions, exports, imports }); return { classes, functions, exports, imports }; } catch (error) { logger.error(`Error parsing TypeScript: ${error instanceof Error ? error.message : String(error)}`); return { classes: [], functions: [], exports: [], imports: [] }; } } /** * Parse non-JS/TS files for a simplified structure * @param content File content * @returns Basic file structure */ parseGenericFile(content) { const lines = content.split('\n'); const imports = []; const functions = []; // Simple regex-based detection for common patterns // This is a simplistic approach; a real implementation would be more sophisticated for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Detect import statements in various languages if (line.match(/^import\s|^#include\s|^from\s.*\simport\s|^require\s/)) { imports.push({ source: line.split(/['"]/).filter(Boolean)[0] || 'unknown', imported: ['*'], startLine: i }); } // Detect function declarations const functionMatch = line.match(/^(function|def|func)\s+(\w+)\s*\(/i); if (functionMatch) { const name = functionMatch[2]; functions.push({ name, params: [], startLine: i, endLine: i + 10, // Estimate function length isMethod: false }); } } return { classes: [], functions, exports: [], imports }; } /** * Traverse the AST to extract code structure * @param ast AST node to process * @param structure Structure being built */ traverseAst(ast, structure) { if (!ast || typeof ast !== 'object') return; // Process node based on its type if (ast.type) { switch (ast.type) { case 'ImportDeclaration': this.processImport(ast, structure.imports); break; case 'ExportNamedDeclaration': case 'ExportDefaultDeclaration': this.processExport(ast, structure.exports); break; case 'ClassDeclaration': case 'ClassExpression': this.processClass(ast, structure.classes, structure.functions); break; case 'FunctionDeclaration': case 'ArrowFunctionExpression': case 'FunctionExpression': this.processFunction(ast, structure.functions); break; case 'MethodDefinition': this.processMethod(ast, 'unknown', [], structure.functions); break; } } // Recursively process all properties for (const key in ast) { if (key === 'parent') continue; // Avoid circular references if (Array.isArray(ast[key])) { ast[key].forEach((node) => this.traverseAst(node, structure)); } else if (ast[key] && typeof ast[key] === 'object') { this.traverseAst(ast[key], structure); } } } /** * Process an import declaration node * @param node Import node * @param imports Collection of imports */ processImport(node, imports) { if (!node.source || !node.source.value) return; const importInfo = { source: node.source.value, imported: [], startLine: node.loc ? node.loc.start.line - 1 : 0 }; if (node.specifiers) { for (const specifier of node.specifiers) { if (specifier.imported) { importInfo.imported.push(specifier.imported.name || 'default'); } else if (specifier.local) { importInfo.imported.push(specifier.local.name || 'default'); } } } imports.push(importInfo); } /** * Process an export declaration node * @param node Export node * @param exports Collection of exports */ processExport(node, exports) { if (node.declaration) { if (node.declaration.id && node.declaration.id.name) { exports.push(node.declaration.id.name); } else if (node.declaration.declarations) { for (const decl of node.declaration.declarations) { if (decl.id && decl.id.name) { exports.push(decl.id.name); } } } } if (node.specifiers) { for (const specifier of node.specifiers) { if (specifier.exported && specifier.exported.name) { exports.push(specifier.exported.name); } } } } /** * Process a class declaration node * @param node Class node * @param classes Collection of classes * @param functions Collection of functions */ processClass(node, classes, functions) { if (!node.id || !node.id.name) return; const className = node.id.name; const classInfo = { name: className, methods: [], properties: [], startLine: node.loc ? node.loc.start.line - 1 : 0, endLine: node.loc ? node.loc.end.line - 1 : 0 }; if (node.body && node.body.body) { for (const member of node.body.body) { if (member.type === 'MethodDefinition') { this.processMethod(member, className, classInfo.methods, functions); } else if (member.type === 'PropertyDefinition') { if (member.key && member.key.name) { classInfo.properties.push(member.key.name); } } } } classes.push(classInfo); } /** * Process a method definition * @param node Method node * @param className Parent class name * @param methods Collection of class methods * @param functions Collection of all functions */ processMethod(node, className, methods, functions) { if (!node.key || !node.key.name) return; const methodName = node.key.name; const methodInfo = { name: methodName, params: this.extractParams(node.value), startLine: node.loc ? node.loc.start.line - 1 : 0, endLine: node.loc ? node.loc.end.line - 1 : 0, isMethod: true, parentClass: className }; methods.push(methodInfo); functions.push(methodInfo); // Add to both collections } /** * Process a function declaration * @param node Function node * @param functions Collection of functions */ processFunction(node, functions) { // Skip anonymous functions without an identifier if ((node.type === 'FunctionDeclaration' && (!node.id || !node.id.name)) || (node.type !== 'FunctionDeclaration' && (!node.id || !node.id.name))) { return; } const functionName = node.id ? node.id.name : 'anonymous'; const functionInfo = { name: functionName, params: this.extractParams(node), startLine: node.loc ? node.loc.start.line - 1 : 0, endLine: node.loc ? node.loc.end.line - 1 : 0, isMethod: false }; functions.push(functionInfo); } /** * Extract function parameters * @param node Function node * @returns Array of parameter names */ extractParams(node) { const params = []; if (!node.params) return params; for (const param of node.params) { if (param.name) { params.push(param.name); } else if (param.left && param.left.name) { params.push(param.left.name); } else if (param.type === 'ObjectPattern') { params.push('{...}'); } else if (param.type === 'ArrayPattern') { params.push('[...]'); } else if (param.type === 'RestElement' && param.argument && param.argument.name) { params.push(`...${param.argument.name}`); } } return params; } } exports.ContextExtractor = ContextExtractor;