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
JavaScript
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;
;