Summary
The React Router route scanner in src/resolution/frameworks/react.ts reads a fixed 400-char window after each <Route and never cuts that window at the next <Route. On the standard React Router v6 nested-route shape (a parent/layout route that has a path but renders <Outlet/> rather than its own element, with children including a pathless index route), the window bleeds into sibling/child tags, producing wrong references edges (route → component). This corrupts caller/blast-radius answers for the mis-attributed component. Verified end-to-end against a real indexed project (built dist, real SQLite DB, real resolver), not just by reading the code.
Root cause
// src/resolution/frameworks/react.ts:114-123
const routeTagRegex = /<Route\b/g;
let routeMatch: RegExpExecArray | null;
while ((routeMatch = routeTagRegex.exec(content)) !== null) {
const window = content.slice(routeMatch.index, routeMatch.index + 400); // <- no cutoff at next <Route
const pathMatch = window.match(/\bpath\s*=\s*["']([^"']+)["']/);
if (!pathMatch) continue; // index/layout routes without a path
const routePath = pathMatch[1]!;
const compMatch =
window.match(/\bcomponent\s*=\s*\{\s*([A-Z][A-Za-z0-9_]*)/) ||
window.match(/\belement\s*=\s*\{\s*<\s*([A-Z][A-Za-z0-9_]*)/);
...
Two failure modes fall out of the shared window:
- A parent route with a
path but no element of its own matches the child's element={<Child/>} inside the 400-char window → a parentPath → Child edge that shouldn't exist (the parent renders <Outlet/>).
- A pathless
index (or layout) route — which should be skipped by the if (!pathMatch) continue guard — instead finds the next sibling's path="..." inside its window, so it is no longer skipped and gets paired with its own element, yielding a fully mismatched siblingPath → indexComponent edge.
The createBrowserRouter data-router scanner at react.ts:156-163 has the same fixed-window shape (300 chars) and the same class of bleed.
Repro (executed end-to-end against a real indexed project)
Fixture App.tsx:
import { Routes, Route } from 'react-router-dom';
function DashboardHome() { return null; }
function Settings() { return null; }
export function App() {
return (
<Routes>
<Route path="/dashboard">
<Route index element={<DashboardHome/>} />
<Route path="settings" element={<Settings/>} />
</Route>
</Routes>
);
}
After CodeGraph.init + indexAll + resolveReferencesBatched, the resolved route → component edges in the DB are:
L7 /dashboard -references-> DashboardHome # WRONG: /dashboard has no element; renders <Outlet/>
L8 settings -references-> DashboardHome # WRONG pair: this is the pathless index route,
# mislabeled "settings" (borrowed from its sibling)
L9 settings -references-> Settings # correct
The route nodes themselves also show the bleed directly: three route nodes are created — /dashboard, settings, settings — a duplicate settings because the index route picked up its sibling's path.
Correct output should be a single edge: settings → Settings (the parent and the index route are both <Outlet/>-rendered and carry no path-bound component edge).
Net: 2 of the 3 emitted edges are wrong, and DashboardHome gets two bogus incoming route references.
Suggested direction
Truncate the scan window at the next <Route occurrence before matching path/element (and likewise cut at the next route-object boundary in the createBrowserRouter scanner). A pathless route whose window is correctly bounded then falls through the existing if (!pathMatch) continue guard as intended.
Environment
main @ 2d72891, package 1.4.1, Node 22.23.1 (Linux). Reproduces on a clean npm ci && npm run build.
Summary
The React Router route scanner in
src/resolution/frameworks/react.tsreads a fixed 400-char window after each<Routeand never cuts that window at the next<Route. On the standard React Router v6 nested-route shape (a parent/layout route that has apathbut renders<Outlet/>rather than its ownelement, with children including a pathlessindexroute), the window bleeds into sibling/child tags, producing wrongreferencesedges (route → component). This corrupts caller/blast-radius answers for the mis-attributed component. Verified end-to-end against a real indexed project (built dist, real SQLite DB, real resolver), not just by reading the code.Root cause
Two failure modes fall out of the shared window:
pathbut no element of its own matches the child'selement={<Child/>}inside the 400-char window → aparentPath → Childedge that shouldn't exist (the parent renders<Outlet/>).index(or layout) route — which should be skipped by theif (!pathMatch) continueguard — instead finds the next sibling'spath="..."inside its window, so it is no longer skipped and gets paired with its ownelement, yielding a fully mismatchedsiblingPath → indexComponentedge.The
createBrowserRouterdata-router scanner atreact.ts:156-163has the same fixed-window shape (300 chars) and the same class of bleed.Repro (executed end-to-end against a real indexed project)
Fixture
App.tsx:After
CodeGraph.init+indexAll+resolveReferencesBatched, the resolvedroute → componentedges in the DB are:The route nodes themselves also show the bleed directly: three route nodes are created —
/dashboard,settings,settings— a duplicatesettingsbecause theindexroute picked up its sibling'spath.Correct output should be a single edge:
settings → Settings(the parent and the index route are both<Outlet/>-rendered and carry no path-bound component edge).Net: 2 of the 3 emitted edges are wrong, and
DashboardHomegets two bogus incoming route references.Suggested direction
Truncate the scan window at the next
<Routeoccurrence before matchingpath/element(and likewise cut at the next route-object boundary in thecreateBrowserRouterscanner). A pathless route whose window is correctly bounded then falls through the existingif (!pathMatch) continueguard as intended.Environment
main@2d72891, package1.4.1, Node 22.23.1 (Linux). Reproduces on a cleannpm ci && npm run build.