Files
codegraph/src/resolution/resurrect-ref.ts
T
Colby Mchenry dd7064698c fix(react-router): read route tables another file hands the router (#2400)
A route table kept in a file of its own produced no routes: the ASP.NET
Core React template's AppRoutes array, which App.js maps into
<Route {...rest}>, a routes array imported into useRoutes(routes) or
createBrowserRouter(routes), and codedthemes' one-object-per-file
[MainRoutes, LoginRoutes]. Route objects were read only from a file that
itself names a data router, so these apps had no routes and their
navigate()/<Link to> navigation reached nothing.

The table's own file cannot tell it from a menu's { path, element } list,
so the evidence is the file that hands it over: a useRoutes /
create*Router argument, or a .map whose callback renders a <Route> from
the item's own fields ({...route}, {...rest}, path={route.path}). A new
FrameworkResolver.crossFileNodes hook keeps those routes, reconciled in
runPostExtract on every index and sync (removal-only syncs included):
new ones are inserted with pending references, ones no longer wanted are
removed with their incoming resolution edges resurrected, unchanged ones
keep their edges. The React pass starts from the files that import React
Router, follows the import to the table and the tables it names
(...ApiAuthorizationRoutes, children: x), each under its mount, and owns
its routes by id (route:<file>:<line>:table:<path>).

Route objects (tables and data routers alike): an index: true route is
the page at its parent's address; a route object around others is their
layout (a references edge marked layout: true, as Angular's), and is not
a page of its own where a child claims its address. A route renders past
a *Wrapper or *Route guard, and through a same-file
lazy(() => import(...)) to the module it loads, never a same-named
component in another app. useRoutes joins the file-level gate.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:27:44 +00:00

39 lines
1.7 KiB
TypeScript

/**
* The reference behind a resolution edge, for an edge whose target is going
* away — shared by every path that removes nodes other files point at.
*/
import type { Edge, Language, ReferenceKind, UnresolvedReference } from '../types';
/**
* Resurrect a resolution edge that is about to be dropped (its target symbol
* was removed, renamed, or its whole file deleted) as the ORIGINAL unresolved
* reference that created it, read from the refName/refKind stamp
* `createEdges` writes into edge metadata. Inserted as status='pending', the
* ref is consumed by the same sync's resolution sweep: it rebinds to an
* alternative definition if one exists, or parks as status='failed' where the
* #1240 retry finds it if the symbol later reappears.
*
* Returns null — drop silently, the pre-#1240 behavior — for edges without a
* refName stamp (created before the stamp existed, or synthesized): rebuilding
* a ref from the target's plain node name would strip the receiver/qualifier
* context the original text carried (`h.greet` → `greet`) and could rebind
* somewhere a full re-index never would. Silent beats wrong.
*/
export function resurrectRefFromDroppedEdge(
e: Edge & { sourceFilePath: string; sourceLanguage: Language }
): UnresolvedReference | null {
const refName = e.metadata?.refName;
if (typeof refName !== 'string' || refName.length === 0) return null;
const refKind = typeof e.metadata?.refKind === 'string' ? (e.metadata.refKind as ReferenceKind) : e.kind;
return {
fromNodeId: e.source,
referenceName: refName,
referenceKind: refKind,
line: e.line ?? 0,
column: e.column ?? 0,
filePath: e.sourceFilePath,
language: e.sourceLanguage,
};
}