function skipQuoted(source, start) { const quote = source[start]; let escaped = false; for (let index = start + 1; index < source.length; index += 1) { const character = source[index]; if (escaped) { escaped = false; } else if (character === "\\") { escaped = true; } else if (character === quote) { return index + 1; } } return source.length; } function readRouteTag(source, start) { const closing = source[start + 1] === "/"; const nameStart = start + (closing ? 2 : 1); const nameMatch = /^[A-Za-z_$][\w$.-]*/u.exec(source.slice(nameStart)); if (!nameMatch || nameMatch[0] !== "Route") return null; let curlyDepth = 0; let quote = null; let escaped = false; for (let index = nameStart + nameMatch[0].length; index < source.length; index += 1) { const character = source[index]; if (quote !== null) { if (escaped) escaped = false; else if (character === "\\") escaped = true; else if (character === quote) quote = null; continue; } if (character === "\"" || character === "'" || character === "`") { quote = character; } else if (character === "{") { curlyDepth += 1; } else if (character === "}" && curlyDepth > 0) { curlyDepth -= 1; } else if (character === ">" && curlyDepth === 0) { const text = source.slice(nameStart + nameMatch[0].length, index); return { closing, selfClosing: !closing && /\/\s*$/u.test(text), attributes: closing ? "" : text.replace(/\/\s*$/u, ""), end: index + 1 }; } } return { error: "AuthenticatedRoutes.tsx contains an unterminated tag." }; } function findStaticPath(attributes) { const pathAttribute = /(?:^|\s)path\s*=\s*(?:"([^"]*)"|'([^']*)')/u.exec(attributes); if (pathAttribute) return { value: pathAttribute[1] ?? pathAttribute[2] }; if (/(?:^|\s)path\s*=\s*\{/u.test(attributes)) return { error: "dynamic" }; return null; } function routePath(parentPath, childPath) { if (childPath === "*") return parentPath ? `${parentPath.replace(/\/$/u, "")}/*` : "*"; if (childPath.startsWith("/")) return childPath.replace(/\/{2,}/gu, "/"); const joined = `${parentPath || ""}/${childPath}`.replace(/\/{2,}/gu, "/"); return joined || "/"; } function routeFeatures(attributes) { const features = new Set(); for (const match of attributes.matchAll(/\bfeatures\s*\?\.\s*([A-Za-z_$][\w$]*)/gu)) { features.add(match[1]); } return [...features].sort(); } function extractRouteStructure(source) { const errors = []; const routeStack = []; const records = []; for (let index = 0; index < source.length;) { if (source.startsWith("//", index)) { const newline = source.indexOf("\n", index + 2); index = newline < 0 ? source.length : newline + 1; continue; } if (source.startsWith("/*", index)) { const commentEnd = source.indexOf("*/", index + 2); index = commentEnd < 0 ? source.length : commentEnd + 2; continue; } if (source[index] === "\"" || source[index] === "'" || source[index] === "`") { index = skipQuoted(source, index); continue; } if (source[index] === "<" && (source[index + 1] === "/" || /[A-Za-z_$]/u.test(source[index + 1] ?? ""))) { const tag = readRouteTag(source, index); if (tag?.error) { errors.push(tag.error); break; } if (tag) { if (tag.closing) { if (routeStack.length === 0) errors.push("AuthenticatedRoutes.tsx closes a without a matching opening tag."); else routeStack.pop(); } else { const parent = routeStack.at(-1); const parentPath = parent?.path ?? ""; const pathAttribute = findStaticPath(tag.attributes); const hasIndex = /(?:^|\s)index(?:\s|=|$)/u.test(tag.attributes); const localFeatures = routeFeatures(tag.attributes); const features = [...new Set([...(parent?.features ?? []), ...localFeatures])].sort(); const administrator = Boolean(parent?.administrator) || /\bsession\.role\s*===?\s*["']Administrator["']/u.test(tag.attributes); if (pathAttribute?.error === "dynamic") { errors.push("AuthenticatedRoutes.tsx uses a dynamic Route path; route-map validation requires a static path."); } if (hasIndex && pathAttribute) errors.push("AuthenticatedRoutes.tsx declares a Route with both index and path."); let fullPath = null; if (hasIndex) fullPath = parentPath || "/"; else if (pathAttribute && !pathAttribute.error) fullPath = routePath(parentPath, pathAttribute.value); if (fullPath !== null) { records.push({ path: fullPath, featureGates: features, auth: administrator ? "administrator" : "authenticated", depth: routeStack.length, parentPath }); } if (!tag.selfClosing) { routeStack.push({ path: fullPath ?? parentPath, features, administrator }); } } index = tag.end; continue; } } index += 1; } if (routeStack.length > 0) errors.push("AuthenticatedRoutes.tsx contains an unclosed tag."); const uniqueRecords = []; for (const record of records) { const priorIndex = uniqueRecords.findIndex((candidate) => candidate.path === record.path); if (priorIndex < 0) { uniqueRecords.push(record); continue; } const prior = uniqueRecords[priorIndex]; const isNestedSamePath = record.parentPath === record.path && record.depth > prior.depth; if (isNestedSamePath) { uniqueRecords[priorIndex] = record; } else { errors.push(`AuthenticatedRoutes.tsx declares duplicate route path ${record.path}.`); } } return { records: uniqueRecords, errors }; } export function extractAuthenticatedRoutes(source) { return extractRouteStructure(source).records; } export function extractAuthenticatedRoutePaths(source) { return extractAuthenticatedRoutes(source).map((route) => route.path); } function mappedFeatureGates(route) { if (Array.isArray(route?.featureGates)) return [...route.featureGates].sort(); return typeof route?.featureGate === "string" ? [route.featureGate] : []; } export function validateRouteMap(routeMap, authenticatedSource, sessionSource) { const errors = []; const { records, errors: routeErrors } = extractRouteStructure(authenticatedSource); errors.push(...routeErrors); const actualPaths = records.map((route) => route.path); const mappedRoutes = Array.isArray(routeMap?.routes) ? routeMap.routes : []; const mappedPaths = mappedRoutes.map((route) => route?.path); if (new Set(mappedPaths).size !== mappedPaths.length) { errors.push("product/route-map.json declares duplicate paths."); } const missing = actualPaths.filter((routePath) => !mappedPaths.includes(routePath)); const stale = mappedPaths.filter((routePath) => !actualPaths.includes(routePath)); if (missing.length > 0) errors.push(`Route map is missing: ${missing.join(", ")}.`); if (stale.length > 0) errors.push(`Route map has stale entries: ${stale.join(", ")}.`); if (routeMap?.sourceOfTruth !== "src/frontend/src/router/AuthenticatedRoutes.tsx") { errors.push("Route map sourceOfTruth must identify AuthenticatedRoutes.tsx."); } const states = routeMap?.sessionStates; const expectedStates = ["loading", "error", "setupRequired", "unauthenticated"]; if (!states || typeof states !== "object" || Array.isArray(states)) { errors.push("Route map must document the pre-route session states."); } else { const actualStates = Object.keys(states).sort(); if (JSON.stringify(actualStates) !== JSON.stringify([...expectedStates].sort())) { errors.push(`Route map session states must be ${expectedStates.join(", ")}.`); } for (const [condition, screen] of Object.entries(states)) { if (typeof screen !== "string" || !sessionSource.includes(`<${screen}`)) { errors.push(`Session screen is not present in App.tsx: ${condition}.`); } } } for (const actualRoute of records) { const mappedRoute = mappedRoutes.find((route) => route?.path === actualRoute.path); if (!mappedRoute) continue; if (mappedRoute.auth !== actualRoute.auth) { errors.push(`${actualRoute.path} must be marked ${actualRoute.auth}-only.`); } const expectedFeatures = actualRoute.featureGates; const mappedFeatures = mappedFeatureGates(mappedRoute); if (JSON.stringify(expectedFeatures) !== JSON.stringify(mappedFeatures)) { const expected = expectedFeatures.length > 0 ? expectedFeatures.join(", ") : "none"; errors.push(`${actualRoute.path} feature-gate registry must be ${expected}.`); } } return errors; }