| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- /**
- * Express/Node.js Framework Resolver
- *
- * Handles Express and general Node.js patterns.
- */
- import { Node } from '../../types';
- import { FrameworkResolver, UnresolvedRef, ResolvedRef, ResolutionContext } from '../types';
- export const expressResolver: FrameworkResolver = {
- name: 'express',
- detect(context: ResolutionContext): boolean {
- // Check for Express in package.json
- const packageJson = context.readFile('package.json');
- if (packageJson) {
- try {
- const pkg = JSON.parse(packageJson);
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
- if (deps.express || deps.fastify || deps.koa || deps.hapi) {
- return true;
- }
- } catch {
- // Invalid JSON
- }
- }
- // Check for common Express patterns
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (
- file.includes('routes') ||
- file.includes('controllers') ||
- file.includes('middleware')
- ) {
- const content = context.readFile(file);
- if (content && (content.includes('express') || content.includes('app.get') || content.includes('router.get'))) {
- return true;
- }
- }
- }
- return false;
- },
- resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
- // Pattern 1: Middleware references
- if (isMiddlewareName(ref.referenceName)) {
- const result = resolveMiddleware(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- // Pattern 2: Controller method references
- const controllerMatch = ref.referenceName.match(/^(\w+)Controller\.(\w+)$/);
- if (controllerMatch) {
- const [, controller, method] = controllerMatch;
- const result = resolveController(controller!, method!, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.85,
- resolvedBy: 'framework',
- };
- }
- }
- // Pattern 3: Service/helper references
- const serviceMatch = ref.referenceName.match(/^(\w+)(Service|Helper|Utils?)\.(\w+)$/);
- if (serviceMatch) {
- const [, name, suffix, method] = serviceMatch;
- const result = resolveService(name! + suffix!, method!, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- return null;
- },
- extractNodes(filePath: string, content: string): Node[] {
- const nodes: Node[] = [];
- const now = Date.now();
- // Extract route definitions
- // app.get('/path', handler) or router.get('/path', handler)
- const routePatterns = [
- /(app|router)\.(get|post|put|patch|delete|all|use)\(\s*['"]([^'"]+)['"]/g,
- ];
- for (const pattern of routePatterns) {
- let match;
- while ((match = pattern.exec(content)) !== null) {
- const [, _obj, method, path] = match;
- const line = content.slice(0, match.index).split('\n').length;
- // Skip middleware use() without paths
- if (method === 'use' && !path?.startsWith('/')) {
- continue;
- }
- 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: detectLanguage(filePath),
- updatedAt: now,
- });
- }
- }
- return nodes;
- },
- };
- /**
- * Check if a name looks like middleware
- */
- function isMiddlewareName(name: string): boolean {
- const middlewarePatterns = [
- /^auth$/i,
- /^authenticate$/i,
- /^authorization$/i,
- /^validate/i,
- /^sanitize/i,
- /^rateLimit/i,
- /^cors$/i,
- /^helmet$/i,
- /^logger$/i,
- /^errorHandler$/i,
- /^notFound$/i,
- /Middleware$/i,
- ];
- return middlewarePatterns.some((p) => p.test(name));
- }
- /**
- * Resolve middleware reference
- */
- function resolveMiddleware(
- name: string,
- context: ResolutionContext
- ): string | null {
- // Look in middleware directories
- const middlewareDirs = ['middleware', 'middlewares', 'src/middleware', 'src/middlewares'];
- for (const dir of middlewareDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (file.startsWith(dir) || file.includes('/middleware/')) {
- const nodes = context.getNodesInFile(file);
- const match = nodes.find(
- (n) =>
- n.name.toLowerCase() === name.toLowerCase() ||
- n.name.toLowerCase() === name.replace(/Middleware$/i, '').toLowerCase()
- );
- if (match) {
- return match.id;
- }
- }
- }
- }
- return null;
- }
- /**
- * Resolve controller method
- */
- function resolveController(
- controller: string,
- method: string,
- context: ResolutionContext
- ): string | null {
- const controllerDirs = ['controllers', 'src/controllers', 'app/controllers'];
- for (const dir of controllerDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (
- (file.startsWith(dir) || file.includes('/controllers/')) &&
- file.toLowerCase().includes(controller.toLowerCase())
- ) {
- const nodes = context.getNodesInFile(file);
- const methodNode = nodes.find(
- (n) => (n.kind === 'method' || n.kind === 'function') && n.name === method
- );
- if (methodNode) {
- return methodNode.id;
- }
- }
- }
- }
- return null;
- }
- /**
- * Resolve service/helper
- */
- function resolveService(
- serviceName: string,
- method: string,
- context: ResolutionContext
- ): string | null {
- const serviceDirs = ['services', 'src/services', 'helpers', 'src/helpers', 'utils', 'src/utils'];
- for (const dir of serviceDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (
- (file.startsWith(dir) || file.includes('/services/') || file.includes('/helpers/') || file.includes('/utils/')) &&
- file.toLowerCase().includes(serviceName.toLowerCase().replace(/(service|helper|utils?)$/i, ''))
- ) {
- const nodes = context.getNodesInFile(file);
- const methodNode = nodes.find(
- (n) => (n.kind === 'method' || n.kind === 'function') && n.name === method
- );
- if (methodNode) {
- return methodNode.id;
- }
- }
- }
- }
- return null;
- }
- /**
- * Detect language from file extension
- */
- function detectLanguage(filePath: string): 'typescript' | 'javascript' {
- if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
- return 'typescript';
- }
- return 'javascript';
- }
|