This commit is contained in:
Colby McHenry
2026-01-18 16:25:00 -06:00
parent 08ccabb5a9
commit cc6e7a5c89
57 changed files with 23315 additions and 1 deletions
+265
View File
@@ -0,0 +1,265 @@
/**
* Rust Framework Resolver
*
* Handles Actix-web, Rocket, Axum, and common Rust patterns.
*/
import { Node } from '../../types';
import { FrameworkResolver, UnresolvedRef, ResolvedRef, ResolutionContext } from '../types';
export const rustResolver: FrameworkResolver = {
name: 'rust',
detect(context: ResolutionContext): boolean {
// Check for Cargo.toml (Rust project signature)
return context.fileExists('Cargo.toml');
},
resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
// Pattern 1: Handler references
if (ref.referenceName.endsWith('_handler') || ref.referenceName.startsWith('handle_')) {
const result = resolveHandler(ref.referenceName, context);
if (result) {
return {
original: ref,
targetNodeId: result,
confidence: 0.8,
resolvedBy: 'framework',
};
}
}
// Pattern 2: Service/Repository trait implementations
if (ref.referenceName.endsWith('Service') || ref.referenceName.endsWith('Repository')) {
const result = resolveService(ref.referenceName, context);
if (result) {
return {
original: ref,
targetNodeId: result,
confidence: 0.8,
resolvedBy: 'framework',
};
}
}
// Pattern 3: Struct references (PascalCase)
if (/^[A-Z][a-zA-Z]+$/.test(ref.referenceName)) {
const result = resolveStruct(ref.referenceName, context);
if (result) {
return {
original: ref,
targetNodeId: result,
confidence: 0.7,
resolvedBy: 'framework',
};
}
}
// Pattern 4: Module references
if (/^[a-z_]+$/.test(ref.referenceName)) {
const result = resolveModule(ref.referenceName, context);
if (result) {
return {
original: ref,
targetNodeId: result,
confidence: 0.6,
resolvedBy: 'framework',
};
}
}
return null;
},
extractNodes(filePath: string, content: string): Node[] {
const nodes: Node[] = [];
const now = Date.now();
// Extract Actix-web routes
// #[get("/path")], #[post("/path")], etc.
const actixRoutePattern = /#\[(get|post|put|patch|delete)\s*\(\s*["']([^"']+)["']/g;
let match;
while ((match = actixRoutePattern.exec(content)) !== null) {
const [, method, path] = match;
const line = content.slice(0, match.index).split('\n').length;
nodes.push({
id: `route:${filePath}:${method!.toUpperCase()}:${path}:${line}`,
kind: 'route',
name: `${method!.toUpperCase()} ${path}`,
qualifiedName: `${filePath}::${method!.toUpperCase()}:${path}`,
filePath,
startLine: line,
endLine: line,
startColumn: 0,
endColumn: match[0].length,
language: 'rust',
updatedAt: now,
});
}
// Extract Rocket routes
// #[get("/path")], #[post("/path", ...)]
const rocketRoutePattern = /#\[(get|post|put|patch|delete|head|options)\s*\(\s*["']([^"']+)["']/g;
while ((match = rocketRoutePattern.exec(content)) !== null) {
const [, method, path] = match;
const line = content.slice(0, match.index).split('\n').length;
// Avoid duplicates from actix pattern
const routeId = `route:${filePath}:${method!.toUpperCase()}:${path}:${line}`;
if (!nodes.some((n) => n.id === routeId)) {
nodes.push({
id: routeId,
kind: 'route',
name: `${method!.toUpperCase()} ${path}`,
qualifiedName: `${filePath}::${method!.toUpperCase()}:${path}`,
filePath,
startLine: line,
endLine: line,
startColumn: 0,
endColumn: match[0].length,
language: 'rust',
updatedAt: now,
});
}
}
// Extract Axum routes (method chaining style)
// .route("/path", get(handler))
const axumRoutePattern = /\.route\s*\(\s*["']([^"']+)["']\s*,\s*(get|post|put|patch|delete)/g;
while ((match = axumRoutePattern.exec(content)) !== null) {
const [, path, method] = match;
const line = content.slice(0, match.index).split('\n').length;
nodes.push({
id: `route:${filePath}:${method!.toUpperCase()}:${path}:${line}`,
kind: 'route',
name: `${method!.toUpperCase()} ${path}`,
qualifiedName: `${filePath}::${method!.toUpperCase()}:${path}`,
filePath,
startLine: line,
endLine: line,
startColumn: 0,
endColumn: match[0].length,
language: 'rust',
updatedAt: now,
});
}
return nodes;
},
};
// Helper functions
function resolveHandler(name: string, context: ResolutionContext): string | null {
const handlerDirs = ['handlers', 'handler', 'api', 'routes', 'controllers'];
const allFiles = context.getAllFiles();
for (const file of allFiles) {
if (file.endsWith('.rs') && handlerDirs.some((d) => file.includes(`/${d}/`) || file.includes(`/${d}.rs`))) {
const nodes = context.getNodesInFile(file);
const handlerNode = nodes.find(
(n) => n.kind === 'function' && n.name === name
);
if (handlerNode) {
return handlerNode.id;
}
}
}
// Search all Rust files
for (const file of allFiles) {
if (file.endsWith('.rs')) {
const nodes = context.getNodesInFile(file);
const handlerNode = nodes.find(
(n) => n.kind === 'function' && n.name === name
);
if (handlerNode) {
return handlerNode.id;
}
}
}
return null;
}
function resolveService(name: string, context: ResolutionContext): string | null {
const serviceDirs = ['services', 'service', 'repository', 'domain'];
const allFiles = context.getAllFiles();
for (const file of allFiles) {
if (file.endsWith('.rs') && serviceDirs.some((d) => file.includes(`/${d}/`) || file.includes(`/${d}.rs`))) {
const nodes = context.getNodesInFile(file);
const serviceNode = nodes.find(
(n) => (n.kind === 'struct' || n.kind === 'trait') && n.name === name
);
if (serviceNode) {
return serviceNode.id;
}
}
}
return null;
}
function resolveStruct(name: string, context: ResolutionContext): string | null {
const modelDirs = ['models', 'model', 'entities', 'entity', 'domain', 'types'];
const allFiles = context.getAllFiles();
// Check model directories first
for (const file of allFiles) {
if (file.endsWith('.rs') && modelDirs.some((d) => file.includes(`/${d}/`) || file.includes(`/${d}.rs`))) {
const nodes = context.getNodesInFile(file);
const structNode = nodes.find(
(n) => n.kind === 'struct' && n.name === name
);
if (structNode) {
return structNode.id;
}
}
}
// Search all Rust files
for (const file of allFiles) {
if (file.endsWith('.rs')) {
const nodes = context.getNodesInFile(file);
const structNode = nodes.find(
(n) => n.kind === 'struct' && n.name === name
);
if (structNode) {
return structNode.id;
}
}
}
return null;
}
function resolveModule(name: string, context: ResolutionContext): string | null {
// Rust modules can be either mod.rs in a directory or name.rs
const possiblePaths = [
`src/${name}.rs`,
`src/${name}/mod.rs`,
];
for (const modPath of possiblePaths) {
if (context.fileExists(modPath)) {
const nodes = context.getNodesInFile(modPath);
const modNode = nodes.find((n) => n.kind === 'module');
if (modNode) {
return modNode.id;
}
// If no explicit module node, return the first node in the file
if (nodes.length > 0) {
return nodes[0]!.id;
}
}
}
return null;
}