mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
fix(viewer): pages that went blank — repeated ids, and Steps on a function full of checks (#2264)
* fix(viewer): a repeated id no longer blanks Entry points or a component's Symbol view The viewer keys its lists by id and a repeat stops the page drawing. The routes list was a row per (route, edge), so an inline handler came back once per call in its body; it is now one row per route — its bound handler, or the route itself as an inline handler. A Vue/Svelte/Astro component's members listed its script's symbols twice, plus the component and its file; each member is now listed once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(viewer): Steps reads a function full of checks in one pass The in-order reading kept every way forward through an `if` with no `else` twice — under its condition and without — so eighty checks that draw nothing (jsoup's `parse`) made 2^80 ways and the page died on a stack overflow. Ways waiting at the same place are merged, keeping the conditions they all share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -140,7 +140,8 @@ export function routeRow(route: WireEntryRoute): EntryRow {
|
||||
target: route.handlerId
|
||||
? { type: 'symbol', id: route.handlerId, name: route.handler, kind: route.handlerKind }
|
||||
: null,
|
||||
flowFrom: route.handlerId ? route.handler : null,
|
||||
// An inline handler has no name of its own to draw a Flow from.
|
||||
flowFrom: route.handlerId && !route.inline ? route.handler : null,
|
||||
title: `${route.url} → ${route.handler} (${route.file}:${route.line}), registered at ${route.routeFile}:${route.routeLine}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,12 +174,12 @@ export function orderGraph(program: NonNullable<WireStepsPayload['program']>, an
|
||||
// An arm that answers, returns or throws does not rejoin — nothing
|
||||
// leaves the last box in it, which is what says so on a canvas.
|
||||
const armTails = flow(arm.body, entry, []);
|
||||
if (arm.ends === null) out.push(...armTails);
|
||||
if (arm.ends === null) for (const t of armTails) out.push(t);
|
||||
}
|
||||
// An `if` with no `else` runs on either way; a fork with both sides
|
||||
// covered runs on only through the arms that did not end.
|
||||
if (item.arms.length < 2) out.push(...tails.map((t) => ({ ...t, runs: [...t.runs, ...runs] })));
|
||||
tails = out;
|
||||
if (item.arms.length < 2) for (const t of tails) out.push({ ...t, runs: [...t.runs, ...runs] });
|
||||
tails = mergeTails(out);
|
||||
} else if (item.kind === 'block') {
|
||||
const label = runWords(item);
|
||||
const inner = flow(item.body, tails, [...runs, label]);
|
||||
@@ -206,6 +206,26 @@ export function orderGraph(program: NonNullable<WireStepsPayload['program']>, an
|
||||
return { edges, depth: rows(anchor, ids, edges), forks };
|
||||
}
|
||||
|
||||
/**
|
||||
* Ways on that wait at the same place are one place to go on from. Without
|
||||
* this, an `if` with no `else` — or a fork whose arms draw nothing — doubles
|
||||
* every way it crosses (once under its condition, once without), and a body
|
||||
* with eighty such checks (jsoup's `parse`) never finished: the page died
|
||||
* overflowing the stack. The merged way keeps what every way to it had to
|
||||
* hold; a condition only some of them needed is dropped, so the line
|
||||
* under-claims rather than claims a side it does not have.
|
||||
*/
|
||||
function mergeTails(tails: readonly Tail[]): Tail[] {
|
||||
const byPlace = new Map<string, Tail>();
|
||||
for (const t of tails) {
|
||||
const key = `${t.id}\0${t.arm ?? ''}\0${t.runs.join('\0')}`;
|
||||
const found = byPlace.get(key);
|
||||
if (found) found.when = found.when.filter((w) => t.when.includes(w));
|
||||
else byPlace.set(key, { ...t, when: [...t.when], runs: [...t.runs] });
|
||||
}
|
||||
return [...byPlace.values()];
|
||||
}
|
||||
|
||||
/** Whether anything in this block draws a box — a fork with one drawn arm is a guard clause, not a point. */
|
||||
function hasStep(block: WireBlock): boolean {
|
||||
return block.some((item) =>
|
||||
|
||||
@@ -377,6 +377,8 @@ export interface WireRoute {
|
||||
routeFile: string;
|
||||
routeLine: number;
|
||||
routeId: string;
|
||||
/** The handler is written inline at the registration: the route stands in for it. */
|
||||
inline: boolean;
|
||||
}
|
||||
|
||||
export interface WireRoutes {
|
||||
@@ -410,6 +412,8 @@ export interface WireEntryRoute {
|
||||
routeFile: string;
|
||||
routeLine: number;
|
||||
routeId: string;
|
||||
/** The handler is written inline at the registration: the route stands in for it. */
|
||||
inline: boolean;
|
||||
}
|
||||
|
||||
export interface WireEntryFile extends WireNodeRef {
|
||||
|
||||
Reference in New Issue
Block a user