Python/FastAPI: scan for APIRouter(prefix=...) + include_router() chains
(
content: string,
_entryDir: string,
files: string[],
project: ProjectInfo,
mountEdges: Map<string, { prefix: string; mountedBy: string }>,
sourceFile: string
)
| 1912 | mountEdges: Map<string, { prefix: string; mountedBy: string }>, |
| 1913 | sourceFile: string |
| 1914 | ): void { |
| 1915 | // Step 1: Build alias map: "auth_router" → "backend/routes/auth.py" |
| 1916 | // from routes.auth import router as auth_router |
| 1917 | const aliasRe = /from\s+([\w.]+)\s+import\s+router\s+as\s+(\w+)/g; |
| 1918 | const aliasMap = new Map<string, string>(); // alias → source file |
| 1919 | let m; |
| 1920 | while ((m = aliasRe.exec(content)) !== null) { |
| 1921 | const moduleDots = m[1]; |
| 1922 | const alias = m[2]; |
| 1923 | const modPath = moduleDots.replace(/\./g, "/"); |
| 1924 | const hit = files.find(f => { |
| 1925 | const rel = f.replace(/\\/g, "/"); |
| 1926 | return rel.endsWith(`/${modPath}.py`) || rel.endsWith(`${modPath}.py`); |
| 1927 | }); |
| 1928 | if (hit) aliasMap.set(alias, relative(project.root, hit).replace(/\\/g, "/")); |
| 1929 | } |
| 1930 | |
| 1931 | // Also handle: from routes.auth import router (no alias) |
| 1932 | const noAliasRe = /from\s+([\w.]+)\s+import\s+router(?!\s+as)\b/g; |
| 1933 | while ((m = noAliasRe.exec(content)) !== null) { |
| 1934 | const modPath = m[1].replace(/\./g, "/"); |
| 1935 | const hit = files.find(f => f.replace(/\\/g, "/").endsWith(`${modPath}.py`)); |
| 1936 | if (hit) aliasMap.set("router", relative(project.root, hit).replace(/\\/g, "/")); |
| 1937 | } |
| 1938 | |
| 1939 | // Step 2: Find APIRouter with prefix: api_router = APIRouter(prefix="/api") |
| 1940 | const prefixRouterRe = /(\w+)\s*=\s*APIRouter\s*\([^)]*prefix\s*=\s*['"]([^'"]+)['"]/g; |
| 1941 | const routerPrefixes = new Map<string, string>(); // varName → prefix |
| 1942 | while ((m = prefixRouterRe.exec(content)) !== null) { |
| 1943 | routerPrefixes.set(m[1], m[2]); |
| 1944 | } |
| 1945 | |
| 1946 | // Step 3: Chain include_router calls: |
| 1947 | // api_router.include_router(auth_router) |
| 1948 | // api_router.include_router(cv_router, prefix="/cv") |
| 1949 | const includeRe = /(\w+)\s*\.\s*include_router\s*\(\s*(\w+)(?:[^)]*prefix\s*=\s*['"]([^'"]+)['"])?\s*\)/g; |
| 1950 | while ((m = includeRe.exec(content)) !== null) { |
| 1951 | const parentVar = m[1]; |
| 1952 | const childVar = m[2]; |
| 1953 | const extraPrefix = m[3] || ""; |
| 1954 | const parentPrefix = routerPrefixes.get(parentVar) || ""; |
| 1955 | const fullPrefix = parentPrefix + extraPrefix; |
| 1956 | |
| 1957 | const targetFile = aliasMap.get(childVar); |
| 1958 | if (targetFile && fullPrefix && !mountEdges.has(targetFile)) { |
| 1959 | mountEdges.set(targetFile, { prefix: fullPrefix, mountedBy: sourceFile }); |
| 1960 | } |
| 1961 | } |
| 1962 | } |
no outgoing calls
no test coverage detected