mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-23 10:21:40 +08:00
b1f40c57dd
- resolution/tier-synthesizer.ts: http-client (literal fetch/axios/ky/got/$fetch paths, axios.create baseURL instances, template holes as :params, base-URL holes by a two-segment tail; unique match only), queue-job (BullMQ/Bull add ↔ @Process/@Processor, WorkerHost process, new Worker, queue.process), event-bus (EventEmitter2 emit ↔ @OnEvent with globs; socket emit ↔ @SubscribeMessage / socket.on both ways with tier); channel, tier, callee, registeredAt on every edge; generic transport events never pair; test and generated files never sources; registered before the emitter pass
- steps.ts: crossing() reads tier/channel before languages; an endpoint reached across a tier is a bridge box and a boundary like a screen (through=1 enters it); a channel's call is not also an effect; sites read as written; a Next 'use server' action is a crossing by its directive (when.ts directive); a function-valued constant handler (asyncHandler(...)) is a route root and borrows the file-scope calls and refs within its lines
- express.ts: app.use('/prefix', router) mounts composed onto route names in postExtract (nested, by import or require); chained router.route('/x').get(h).put(h2) extracted, across lines
- frameworks/package-deps.ts: dependencies read from workspace package.json files too (Express, React, Expo Router, NestJS detect)
- routing manifest names constant handlers; e2e/ is a test directory; explore's Flow section labels the new channels
- tests: ui-steps-cross-tier (monorepo fixture: Next client + Express/Nest API), servers test updated for the queue landing
- docs: CHANGELOG, spec §3.13 cross-tier paragraph, CLAUDE.md, callback-edge-synthesis.md, plan P3 built
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
/**
|
|
* Small helpers the dynamic-edge synthesizers share: a per-match line
|
|
* resolver over one source string, and "the function this line is in".
|
|
*/
|
|
|
|
import type { Node } from '../types';
|
|
|
|
/** The kinds a call site can be attributed to. */
|
|
export const FN_KINDS: ReadonlySet<string> = new Set(['method', 'function', 'component']);
|
|
|
|
/**
|
|
* Per-match line resolver over `src`, 1-based at `baseLine`. The inline
|
|
* `src.slice(0, idx).split('\n').length` idiom is O(source-length) PER MATCH,
|
|
* which goes quadratic on a match-dense source (a generated function full of
|
|
* `.push(` calls re-scanned tens of thousands of times was most of the #1235
|
|
* indexing wedge). Builds the newline index once — lazily, since most sources
|
|
* never produce a match — then answers each call with a binary search.
|
|
*/
|
|
export function makeLineAt(src: string, baseLine: number): (idx: number) => number {
|
|
let nl: number[] | null = null;
|
|
return (idx: number) => {
|
|
if (!nl) {
|
|
nl = [];
|
|
for (let i = src.indexOf('\n'); i !== -1; i = src.indexOf('\n', i + 1)) nl.push(i);
|
|
}
|
|
// Count newlines strictly before idx.
|
|
let lo = 0;
|
|
let hi = nl.length;
|
|
while (lo < hi) {
|
|
const mid = (lo + hi) >> 1;
|
|
if (nl[mid]! < idx) lo = mid + 1;
|
|
else hi = mid;
|
|
}
|
|
return baseLine + lo;
|
|
};
|
|
}
|
|
|
|
/** Innermost function/method node whose line range contains `line`. */
|
|
export function enclosingFn(nodesInFile: readonly Node[], line: number): Node | null {
|
|
let best: Node | null = null;
|
|
for (const n of nodesInFile) {
|
|
if (!FN_KINDS.has(n.kind)) continue;
|
|
const end = n.endLine ?? n.startLine;
|
|
if (n.startLine <= line && end >= line) {
|
|
if (!best || n.startLine >= best.startLine) best = n; // prefer the tightest (latest-starting) encloser
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/**
|
|
* The smallest `constant` / `variable` node whose lines contain `line` — the
|
|
* value a top-level registration is written inside (`const worker = new
|
|
* Worker('q', async (job) => …)`): what a caller imports and a walk can start
|
|
* from when no function encloses the site.
|
|
*/
|
|
export function enclosingValue(nodesInFile: readonly Node[], line: number): Node | null {
|
|
let best: Node | null = null;
|
|
for (const n of nodesInFile) {
|
|
if (n.kind !== 'constant' && n.kind !== 'variable') continue;
|
|
const end = n.endLine ?? n.startLine;
|
|
if (n.startLine <= line && end >= line) {
|
|
if (!best || n.startLine >= best.startLine) best = n;
|
|
}
|
|
}
|
|
return best;
|
|
}
|