diff --git a/src/domain/javascript/javascriptRouteInventory.test.ts b/src/domain/javascript/javascriptRouteInventory.test.ts index 028a5282a1..de9653feef 100644 --- a/src/domain/javascript/javascriptRouteInventory.test.ts +++ b/src/domain/javascript/javascriptRouteInventory.test.ts @@ -204,6 +204,33 @@ it("binds ordinary constants to application segments across sources and excludes ]); expect(result.statistics.unanchored_literals_excluded).toBe(2); }); +it.each(["api", "assets", "static", "_next"])( + "retains constants under an anchored %s segment without promoting unanchored paths", + (prefix) => { + const result = build([ + source("route.js", `router.push('/${prefix}/status');`), + source( + "runtime.js", + `globalThis.TURBOPACK=[];const same='/${prefix}/status';const help='/${prefix}/help';const other='/unanchored/noise';`, + ), + ]); + const anchored = result.routes.find( + ({ path }) => path === `/${prefix}/status`, + ); + const descendant = result.routes.find( + ({ path }) => path === `/${prefix}/help`, + ); + expect(anchored).toMatchObject({ occurrence_count: 2, source_count: 2 }); + expect(descendant).toMatchObject({ + confidence: "low", + classification: "unknown", + }); + expect(result.routes.some(({ path }) => path === "/unanchored/noise")).toBe( + false, + ); + expect(result.coverage.application_routes).toBe("unknown"); + }, +); it("retains inferred Next script bases and the unprefixed path", () => { const result = build([ source( diff --git a/src/domain/javascript/javascriptRouteScan.ts b/src/domain/javascript/javascriptRouteScan.ts index 32a4193597..e3e1f38992 100644 --- a/src/domain/javascript/javascriptRouteScan.ts +++ b/src/domain/javascript/javascriptRouteScan.ts @@ -831,11 +831,7 @@ export class JavaScriptRouteScan { // Decide generic-constant admission only after all specific owners finish. if (this.#propertyValues.has(literal.node)) continue; const path = routePath(literal.value, { navigation: true }); - if ( - path === undefined || - /^\/(?:_next|api|assets|static)(?:\/|$)/u.test(path) - ) - continue; + if (path === undefined) continue; // These remain inferred candidates. The inventory owner additionally binds them to app roots across sources. if (this.#nextRuntime || roots.has(path.split("/")[1]!)) this.#emit( diff --git a/tests/acceptance/applications/javascriptRouteAndRpcIdentity.test.ts b/tests/acceptance/applications/javascriptRouteAndRpcIdentity.test.ts index 61f489d797..b9c78599cf 100644 --- a/tests/acceptance/applications/javascriptRouteAndRpcIdentity.test.ts +++ b/tests/acceptance/applications/javascriptRouteAndRpcIdentity.test.ts @@ -38,3 +38,25 @@ cliTest.for(["CLI", "stdio MCP"] as const)( expect(operations).toEqual(["archive", "history.list"]); }, ); + +cliTest.for(["CLI", "stdio MCP"] as const)( + "retains anchored reserved-prefix route constants through %s", + async (surface, { cli }) => { + const root = await createTestTempDirectory("rea-route-reserved-prefix-"); + await writeFixtureFiles(root, { + "entry.js": `globalThis.TURBOPACK=[]; + import {createBrowserRouter} from 'react-router-dom'; + export const router=createBrowserRouter([{path:'/static',element:null}]); + const help='/static/help';globalThis.helpLink=help; + const unrelated='/api/unanchored';globalThis.other=unrelated;`, + }); + const result = await analyzeJavaScriptDirectory(surface, cli, root); + const routes = result.route_inventory?.routes ?? []; + expect(routes.find(({ path }) => path === "/static/help")).toMatchObject({ + confidence: "low", + classification: "unknown", + }); + expect(routes.some(({ path }) => path === "/api/unanchored")).toBe(false); + expect(result.route_inventory?.coverage.application_routes).toBe("unknown"); + }, +);