Skip to content

React Router: fixed 400-char <Route> scan window bleeds element across sibling/child routes, producing wrong references edges — verified end-to-end #1348

Description

@inth3shadows

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:

  1. 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/>).
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions