(queries: QueryBuilder, ctx: ResolutionContext, onYield: MaybeYield)
| 1933 | } |
| 1934 | |
| 1935 | async function ginMiddlewareChainEdges(queries: QueryBuilder, ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]> { |
| 1936 | let scanned255 = 0; |
| 1937 | let scannedFiles = 0; |
| 1938 | // 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index. |
| 1939 | const dispatchers: Node[] = []; |
| 1940 | for (const n of queries.iterateNodesByKind('method')) { |
| 1941 | if ((++scanned255 & 63) === 0) await onYield(); |
| 1942 | if (n.language !== 'go') continue; |
| 1943 | const content = ctx.readFile(n.filePath); |
| 1944 | const src = content && sliceLines(content, n.startLine, n.endLine); |
| 1945 | if (src && GIN_DISPATCH_RE.test(src)) dispatchers.push(n); |
| 1946 | } |
| 1947 | if (dispatchers.length === 0) return []; // not a gin repo — bail |
| 1948 | |
| 1949 | // 2. Collect handler identifiers registered via gin registration calls |
| 1950 | // (.Use / .GET / … / .Handle). String args (paths/methods) and inline |
| 1951 | // closures are dropped by goHandlerIdent; the rest are HandlerFuncs. |
| 1952 | const registered = new Map<string, string>(); // name → registeredAt (file:line) |
| 1953 | for (const file of ctx.getAllFiles()) { |
| 1954 | if ((++scannedFiles & 15) === 0) await onYield(); |
| 1955 | if (!file.endsWith('.go')) continue; |
| 1956 | const content = ctx.readFile(file); |
| 1957 | if (!content || (!content.includes('.Use(') && !/\.(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\(/.test(content))) continue; |
| 1958 | const safe = stripCommentsForRegex(content, 'go'); |
| 1959 | GIN_REG_RE.lastIndex = 0; |
| 1960 | let m: RegExpExecArray | null; |
| 1961 | while ((m = GIN_REG_RE.exec(safe))) { |
| 1962 | const parenIdx = m.index + m[0].length - 1; |
| 1963 | const argStr = goBalancedArgs(safe, parenIdx); |
| 1964 | if (!argStr) continue; |
| 1965 | const line = safe.slice(0, m.index).split('\n').length; |
| 1966 | for (const arg of goSplitArgs(argStr)) { |
| 1967 | const name = goHandlerIdent(arg); |
| 1968 | if (name && !registered.has(name)) registered.set(name, `${file}:${line}`); |
| 1969 | } |
| 1970 | } |
| 1971 | } |
| 1972 | if (registered.size === 0) return []; |
| 1973 | |
| 1974 | // 3. Link each dispatcher → each registered handler node (dedup, capped). |
| 1975 | const edges: Edge[] = []; |
| 1976 | const seen = new Set<string>(); |
| 1977 | for (const disp of dispatchers) { |
| 1978 | let added = 0; |
| 1979 | for (const [name, registeredAt] of registered) { |
| 1980 | if (added >= MAX_CALLBACKS_PER_CHANNEL) break; |
| 1981 | const handler = ctx.getNodesByName(name).find( |
| 1982 | (n) => (n.kind === 'function' || n.kind === 'method') && n.language === 'go' |
| 1983 | ); |
| 1984 | if (!handler || handler.id === disp.id) continue; |
| 1985 | const key = `${disp.id}>${handler.id}`; |
| 1986 | if (seen.has(key)) continue; |
| 1987 | seen.add(key); |
| 1988 | edges.push({ |
| 1989 | source: disp.id, target: handler.id, kind: 'calls', line: disp.startLine, |
| 1990 | provenance: 'heuristic', |
| 1991 | metadata: { synthesizedBy: 'gin-middleware-chain', via: name, registeredAt }, |
| 1992 | }); |
no test coverage detected