UNPKG

express-router-dependency-graph

Version:
135 lines 5.02 kB
import { parse } from "@babel/parser"; import path from "node:path"; import fs from "node:fs/promises"; import query from "esquery"; import { markdownTable } from "markdown-table"; const findRouting = async ({ AST, fileContent }) => { try { const search = (method, AST) => { const selector = `CallExpression:has(MemberExpression > Identifier[name="${method}"])`; const results = query(AST, selector); // router.{get,post,delete,put,use} return results.flatMap((node) => { // TODO: improve query to avoid this check // res.set("X-Content-Type-Options", req.get("test"))); if (node.callee.property.name !== method) { return []; } // single argument should be ignored // req.get("host"); it is not routing if (node.arguments.length === 1) { return []; } const pathValue = node.arguments[0] !== undefined && node.arguments[0].type === "StringLiteral" && node.arguments[0].value; if (!pathValue) { return []; // skip: it will only includes middleware } const middlewareArguments = method === "use" ? // @ts-ignore node.arguments?.slice(1) ?? [] : // @ts-ignore node.arguments?.slice(1, node.arguments.length - 1) ?? []; const middlewares = middlewareArguments.map((arg) => { // app.use(() => {}); if (arg.type === "ArrowFunctionExpression") { return "Anonymous Function"; } // app.use(function () {}); if (arg.type === "FunctionExpression") { // @ts-ignore return arg?.id?.name ?? "Anonymous Function"; } return fileContent.slice(arg.start, arg.end); }); return [ { method, path: pathValue, middlewares, // @ts-ignore range: [node.start, node.end], // @ts-ignore loc: node.loc } ]; }); }; const methods = ["get", "post", "delete", "put", "use"]; return methods.flatMap((method) => { return search(method, AST); }); } catch { return []; } }; const toAbsolute = (cwd, f) => { return path.resolve(cwd, f); }; const hasImportExpress = (AST) => { // import express from "express"; if (query(AST, "ImportDeclaration[source.value='express']").length > 0) { return true; } // const express = require("express"); if (query(AST, "CallExpression[callee.name='require'][arguments.0.value='express']").length > 0) { return true; } // const express = await import("express"); if (query(AST, "ImportExpression[source.value='express']").length > 0) { return true; } return false; }; export async function analyzeDependency({ filePath }) { const fileContent = await fs.readFile(filePath, "utf-8"); try { const AST = parse(fileContent, { sourceType: "module", plugins: ["jsx", "typescript"] }); if (!hasImportExpress(AST)) { return []; } return findRouting({ AST, fileContent }); } catch (e) { console.error("Error while analyzing", filePath); console.error(e); return []; } } export async function analyzeDependencies({ filePaths, cwd }) { return Promise.all(filePaths.map(async (filePath) => { const absoluteFilePath = toAbsolute(cwd, filePath); return { filePath: absoluteFilePath, routers: await analyzeDependency({ filePath: absoluteFilePath }) }; })); } export const formatMarkdown = ({ cwd, results, rootBaseUrl }) => { const toRelative = (f) => { return path.relative(cwd, f); }; const table = [["File", "Method", "Routing", "Middlewares", "FilePath"]]; for (const result of results) { if (result.routers.length === 0) { continue; } table.push([`${rootBaseUrl}${toRelative(result.filePath)}`]); result.routers.forEach((router) => { table.push([ "", router.method, router.path, router.middlewares.join(", ").split(/\r?\n/g).join(" "), `${rootBaseUrl}${toRelative(result.filePath)}#L${router.loc.start.line}-L${router.loc.end.line}` ]); }); } return markdownTable(table); }; //# sourceMappingURL=index.js.map