(queries: QueryBuilder, ctx: ResolutionContext)
| 1522 | } |
| 1523 | |
| 1524 | function ginMiddlewareChainEdges(queries: QueryBuilder, ctx: ResolutionContext): Edge[] { |
| 1525 | // 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index. |
| 1526 | const dispatchers: Node[] = []; |
| 1527 | for (const n of queries.iterateNodesByKind('method')) { |
| 1528 | if (n.language !== 'go') continue; |
| 1529 | const content = ctx.readFile(n.filePath); |
| 1530 | const src = content && sliceLines(content, n.startLine, n.endLine); |
| 1531 | if (src && GIN_DISPATCH_RE.test(src)) dispatchers.push(n); |
| 1532 | } |
| 1533 | if (dispatchers.length === 0) return []; // not a gin repo — bail |
| 1534 | |
| 1535 | // 2. Collect handler identifiers registered via gin registration calls |
| 1536 | // (.Use / .GET / … / .Handle). String args (paths/methods) and inline |
| 1537 | // closures are dropped by goHandlerIdent; the rest are HandlerFuncs. |
| 1538 | const registered = new Map<string, string>(); // name → registeredAt (file:line) |
| 1539 | for (const file of ctx.getAllFiles()) { |
| 1540 | if (!file.endsWith('.go')) continue; |
| 1541 | const content = ctx.readFile(file); |
| 1542 | if (!content || (!content.includes('.Use(') && !/\.(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\(/.test(content))) continue; |
| 1543 | const safe = stripCommentsForRegex(content, 'go'); |
| 1544 | GIN_REG_RE.lastIndex = 0; |
| 1545 | let m: RegExpExecArray | null; |
| 1546 | while ((m = GIN_REG_RE.exec(safe))) { |
| 1547 | const parenIdx = m.index + m[0].length - 1; |
| 1548 | const argStr = goBalancedArgs(safe, parenIdx); |
| 1549 | if (!argStr) continue; |
| 1550 | const line = safe.slice(0, m.index).split('\n').length; |
| 1551 | for (const arg of goSplitArgs(argStr)) { |
| 1552 | const name = goHandlerIdent(arg); |
| 1553 | if (name && !registered.has(name)) registered.set(name, `${file}:${line}`); |
| 1554 | } |
| 1555 | } |
| 1556 | } |
| 1557 | if (registered.size === 0) return []; |
| 1558 | |
| 1559 | // 3. Link each dispatcher → each registered handler node (dedup, capped). |
| 1560 | const edges: Edge[] = []; |
| 1561 | const seen = new Set<string>(); |
| 1562 | for (const disp of dispatchers) { |
| 1563 | let added = 0; |
| 1564 | for (const [name, registeredAt] of registered) { |
| 1565 | if (added >= MAX_CALLBACKS_PER_CHANNEL) break; |
| 1566 | const handler = ctx.getNodesByName(name).find( |
| 1567 | (n) => (n.kind === 'function' || n.kind === 'method') && n.language === 'go' |
| 1568 | ); |
| 1569 | if (!handler || handler.id === disp.id) continue; |
| 1570 | const key = `${disp.id}>${handler.id}`; |
| 1571 | if (seen.has(key)) continue; |
| 1572 | seen.add(key); |
| 1573 | edges.push({ |
| 1574 | source: disp.id, target: handler.id, kind: 'calls', line: disp.startLine, |
| 1575 | provenance: 'heuristic', |
| 1576 | metadata: { synthesizedBy: 'gin-middleware-chain', via: name, registeredAt }, |
| 1577 | }); |
| 1578 | added++; |
| 1579 | } |
| 1580 | } |
| 1581 | return edges; |
no test coverage detected