(cg: CodeGraph, projectRoot: string, query: URLSearchParams)
| 395 | } |
| 396 | |
| 397 | export async function buildSteps(cg: CodeGraph, projectRoot: string, query: URLSearchParams): Promise<WireStepsPayload> { |
| 398 | const started = Date.now(); |
| 399 | const depthCap = intParam(query, 'depth', { min: 1, max: MAX_DEPTH, default: DEFAULT_DEPTH }); |
| 400 | const limit = intParam(query, 'limit', { min: 20, max: MAX_LIMIT, default: DEFAULT_LIMIT }); |
| 401 | const through = query.get('through') === '1'; |
| 402 | const stats = cg.getStats(); |
| 403 | const index = { lastIndexedAt: cg.getLastIndexedAt() ?? null, edges: stats.edgeCount, files: stats.fileCount }; |
| 404 | |
| 405 | const { anchor, ambiguous } = resolveAnchor(cg, query); |
| 406 | |
| 407 | // Route → where its code starts: the handler a resolver named, the page a |
| 408 | // screen file exports, or the route itself standing in for an inline |
| 409 | // handler (`route-roots.ts`) — and what kind of project this is, for the |
| 410 | // words the viewer uses. |
| 411 | const routes = cg.getNodesByKind('route'); |
| 412 | const roots = routeRoots(cg, routes); |
| 413 | const project = projectKind(routes, stats.edgesByKind?.navigates ?? 0); |
| 414 | |
| 415 | const reader = createSiteReader(cg, projectRoot, MAX_WHEN_SITES); |
| 416 | const calls = createSiteReader(cg, projectRoot, MAX_CALL_SITES); |
| 417 | /** The conditions a site runs under, structured — one read, joined where a string is wanted. */ |
| 418 | const guardsAt = (caller: Node, site: { line?: number; column?: number }) => reader.guards(caller, site); |
| 419 | /** The loops a site is written inside — a run of calls that happens once per item. */ |
| 420 | const loopsAt = (caller: Node, site: { line?: number; column?: number }) => reader.loops(caller, site); |
| 421 | const argsAt = (caller: Node, site: { line?: number; column?: number }) => reader.args(caller, site); |
| 422 | const withArgs = async (site: WireStepSite, caller: Node, at: { line?: number; column?: number }): Promise<WireStepSite> => { |
| 423 | const args = await argsAt(caller, at); |
| 424 | return args === null ? site : { ...site, args }; |
| 425 | }; |
| 426 | /** The call as written at a site, and what it passes — one read for both. */ |
| 427 | const callAt = (caller: Node, site: { line?: number; column?: number; callee?: string }) => calls.callSite(caller, site); |
| 428 | /** A hop's position with its call's span and enclosing call, read from the tree; the bare position when unreadable. */ |
| 429 | const hopAt = async (caller: Node, at: { line?: number; column?: number }, callee?: string): Promise<HopSite> => { |
| 430 | const line = at.line ?? caller.startLine; |
| 431 | const column = at.column ?? 0; |
| 432 | const read = at.line ? await callAt(caller, { ...at, ...(callee ? { callee } : {}) }) : null; |
| 433 | // The read must be THIS call. An inline handler's edges carry the ROUTE's |
| 434 | // line (`router.post('/users/login', async (req, res) => {`), where the |
| 435 | // only call is the registration itself — and taking its span would make |
| 436 | // every call in the handler read as written inside it, and so as running |
| 437 | // first. A miss is a bare position: no span to nest by, no `within`. |
| 438 | const last = (n: string) => n.replace(/\([^()]*\)/g, '').split(/[.:]/).pop() ?? n; |
| 439 | const usable = read !== null && (!callee || last(read.callee) === last(callee)); |
| 440 | if (!usable) return { file: caller.filePath, line, column, end: { line, column }, within: null }; |
| 441 | return { |
| 442 | file: caller.filePath, |
| 443 | line: read.span?.start.line ?? line, |
| 444 | column: read.span?.start.column ?? column, |
| 445 | end: read.span?.end ?? { line, column }, |
| 446 | within: read.within ?? null, |
| 447 | }; |
| 448 | }; |
| 449 | const pointHop = (caller: Node, at: { line?: number; column?: number }): HopSite => { |
| 450 | const line = at.line ?? caller.startLine; |
| 451 | const column = at.column ?? 0; |
| 452 | return { file: caller.filePath, line, column, end: { line, column }, within: null }; |
| 453 | }; |
| 454 |
no test coverage detected