Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(_tools): update dependency graph output #3615

Merged
merged 1 commit into from
Sep 4, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 81 additions & 46 deletions _tools/check_circular_submodule_dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,59 @@ import {
* When run with `--graph` it will output a graphviz graph in dot language.
*/

type DepState = "ready" | "not ready" | "needs clean up" | "deprecated";
type Dep = {
name: string;
set: Set<string>;
state: DepState;
};

const root = new URL("../", import.meta.url).href;
const deps: Record<string, Set<string>> = {};
const deps: Record<string, Dep> = {};

function getSubmoduleName(url: string) {
function getSubmoduleNameFromUrl(url: string) {
return url.replace(root, "").split("/")[0];
}

async function check(submod: string, paths: string[] = ["mod.ts"]) {
async function check(
submod: string,
state: DepState,
paths: string[] = ["mod.ts"],
): Promise<Dep> {
const deps = new Set<string>();
for (const path of paths) {
const entrypoint = new URL(`../${submod}/${path}`, import.meta.url).href;
const graph = await createGraph(entrypoint);

for (const dep of new Set(getDeps(graph, entrypoint))) {
for (
const dep of new Set(getSubmoduleDepsFromSpecifier(graph, entrypoint))
) {
deps.add(dep);
}
}
deps.delete(submod);
deps.delete("types.d.ts");
return deps;
return { name: submod, set: deps, state };
}

/** Returns submodule dependencies */
function getDeps(
function getSubmoduleDepsFromSpecifier(
graph: ModuleGraphJson,
specifier: string,
seen: Set<string> = new Set(),
): Set<string> {
const { dependencies } = graph.modules.find((item: ModuleJson) =>
item.specifier === specifier
)!;
const deps = new Set([getSubmoduleName(specifier)]);
const deps = new Set([getSubmoduleNameFromUrl(specifier)]);
seen.add(specifier);
if (dependencies) {
for (const { code, type } of dependencies) {
const specifier = code?.specifier ?? type?.specifier!;
if (seen.has(specifier)) {
continue;
}
const res = getDeps(
const res = getSubmoduleDepsFromSpecifier(
graph,
specifier,
seen,
Expand All @@ -63,17 +76,17 @@ function getDeps(
return deps;
}

deps["archive"] = await check("archive");
deps["assert"] = await check("assert");
deps["async"] = await check("async");
deps["bytes"] = await check("bytes");
deps["collections"] = await check("collections");
deps["console"] = await check("console");
deps["crypto"] = await check("crypto");
deps["csv"] = await check("csv");
deps["datetime"] = await check("datetime");
deps["dotenv"] = await check("dotenv");
deps["encoding"] = await check("encoding", [
deps["archive"] = await check("archive", "not ready");
deps["assert"] = await check("assert", "ready");
deps["async"] = await check("async", "ready");
deps["bytes"] = await check("bytes", "ready");
deps["collections"] = await check("collections", "needs clean up");
deps["console"] = await check("console", "not ready");
deps["crypto"] = await check("crypto", "needs clean up");
deps["csv"] = await check("csv", "ready");
deps["datetime"] = await check("datetime", "deprecated");
deps["dotenv"] = await check("dotenv", "not ready");
deps["encoding"] = await check("encoding", "ready", [
"ascii85.ts",
"base32.ts",
"base58.ts",
Expand All @@ -83,41 +96,43 @@ deps["encoding"] = await check("encoding", [
"hex.ts",
"varint.ts",
]);
deps["flags"] = await check("flags");
deps["fmt"] = await check("fmt", [
deps["flags"] = await check("flags", "not ready");
deps["fmt"] = await check("fmt", "ready", [
"bytes.ts",
"colors.ts",
"duration.ts",
"printf.ts",
]);
deps["front_matter"] = await check("front_matter");
deps["fs"] = await check("fs");
deps["html"] = await check("html");
deps["http"] = await check("http");
deps["io"] = await check("io");
deps["json"] = await check("json");
deps["jsonc"] = await check("jsonc");
deps["log"] = await check("log");
deps["media_types"] = await check("media_types");
deps["msgpack"] = await check("msgpack");
deps["path"] = await check("path");
deps["permissions"] = await check("permissions");
deps["regexp"] = await check("regexp");
deps["semver"] = await check("semver");
deps["signal"] = await check("signal");
deps["streams"] = await check("streams");
deps["testing"] = await check("testing", [
deps["front_matter"] = await check("front_matter", "needs clean up");
deps["fs"] = await check("fs", "ready");
deps["html"] = await check("html", "not ready");
deps["http"] = await check("http", "needs clean up");
deps["io"] = await check("io", "deprecated");
deps["json"] = await check("json", "ready");
deps["jsonc"] = await check("jsonc", "ready");
deps["log"] = await check("log", "not ready");
deps["media_types"] = await check("media_types", "needs clean up");
deps["msgpack"] = await check("msgpack", "not ready");
deps["path"] = await check("path", "needs clean up");
deps["permissions"] = await check("permissions", "deprecated");
deps["regexp"] = await check("regexp", "not ready");
deps["semver"] = await check("semver", "not ready");
deps["signal"] = await check("signal", "deprecated");
deps["streams"] = await check("streams", "needs clean up");
deps["testing"] = await check("testing", "needs clean up", [
"bdd.ts",
"mock.ts",
"snapshot.ts",
"time.ts",
"types.ts",
]);
deps["toml"] = await check("toml");
deps["uuid"] = await check("uuid");
deps["wasi"] = await check("wasi", ["snapshot_preview1.ts"]);
deps["yaml"] = await check("yaml");
deps["toml"] = await check("toml", "ready");
deps["url"] = await check("url", "not ready");
deps["uuid"] = await check("uuid", "ready");
deps["wasi"] = await check("wasi", "not ready", ["snapshot_preview1.ts"]);
deps["yaml"] = await check("yaml", "ready");

/** Checks circular deps between sub modules */
function checkCircularDeps(
submod: string,
ancestors: string[] = [],
Expand All @@ -131,20 +146,40 @@ function checkCircularDeps(
if (!d) {
return;
}
for (const mod of d) {
for (const mod of d.set) {
const res = checkCircularDeps(mod, currentDeps, seen);
if (res) {
return res;
}
}
}

/** Formats label for diagram */
function formatLabel(mod: string) {
return '"' + mod.replace(/_/g, "_\\n") + '"';
}

/** Returns node style (in DOT language) for each state */
function stateToNodeStyle(state: DepState) {
switch (state) {
case "ready":
return "[shape=doublecircle fixedsize=1 height=1.1]";
case "not ready":
return "[shape=box style=filled, fillcolor=pink]";
case "needs clean up":
return "[shape=circle fixedsize=1 height=1.1 style=filled, fillcolor=yellow]";
case "deprecated":
return "[shape=septagon style=filled, fillcolor=gray]";
}
}

if (Deno.args.includes("--graph")) {
console.log("digraph std_deps {");
for (const mod of Object.keys(deps)) {
console.log(` ${mod};`);
for (const dep of deps[mod]) {
console.log(` ${mod} -> ${dep};`);
const info = deps[mod];
console.log(` ${formatLabel(mod)} ${stateToNodeStyle(info.state)};`);
for (const dep of deps[mod].set) {
console.log(` ${formatLabel(mod)} -> ${dep};`);
}
}
console.log("}");
Expand Down