shared/analyzers/liveness.analyzer.ts
shared/analyzers/liveness.analyzer.ts is a file in GovLab Extension Host. 92 lines of code and 19 definitions.
import type { BarrelPattern, ClosureGraph, ExportEntry, ImportGraph, Reachable } from "../../types/closure.types.ts";
import { matchesBarrelPattern } from "../loaders/barrel.loader.ts";
import { normalizeImport } from "../loaders/graph.loader.ts";
const addEdge = function addEdge(graph: ImportGraph, file: string, from: string, names: string[]): void {
const target = normalizeImport(file, from);
if (target === null) {
return;
}
const edges = graph.get(file) ?? [];
edges.push({ names, target });
graph.set(file, edges);
};
export const buildImportGraph = function buildImportGraph(closure: ClosureGraph): ImportGraph {
const graph: ImportGraph = new Map();
for (const imp of closure.imports) {
addEdge(graph, imp.file, imp.from, imp.names);
}
for (const imp of closure.sideEffectImports) {
addEdge(graph, imp.file, imp.from, []);
}
return graph;
};
export const findEntrypoints = function findEntrypoints(
closure: ClosureGraph,
entrySuffixes: readonly string[],
barrels: readonly BarrelPattern[],
): Set<string> {
const seen = new Set<string>();
const allFiles = new Set<string>();
for (const e of closure.exports) {
allFiles.add(e.file);
}
for (const imp of closure.imports) {
allFiles.add(imp.file);
}
for (const file of allFiles) {
if (entrySuffixes.some((suffix) => file.endsWith(suffix)) || matchesBarrelPattern(file, barrels)) {
seen.add(file);
}
}
return seen;
};
export const walkReachable = function walkReachable(entrypoints: Iterable<string>, graph: ImportGraph): Reachable {
const reachableFiles = new Set<string>();
const reachableExports = new Set<string>();
const stack = [...entrypoints];
while (stack.length > 0) {
const file = stack.pop();
if (file === undefined || reachableFiles.has(file)) {
continue;
}
reachableFiles.add(file);
for (const imp of graph.get(file) ?? []) {
for (const name of imp.names) {
reachableExports.add(`${imp.target}::${name}`);
}
stack.push(imp.target);
}
}
return { reachableExports, reachableFiles };
};
const externalReach = function externalReach(closure: ClosureGraph, graph: ImportGraph): string[] {
const keys: string[] = [];
for (const imp of closure.externalConsumers) {
const target = normalizeImport(imp.file, imp.from);
if (target === null) {
continue;
}
for (const name of imp.names) {
keys.push(`${target}::${name}`);
}
keys.push(...walkReachable([target], graph).reachableExports);
}
return keys;
};
export const computeDeadExports = function computeDeadExports(
closure: ClosureGraph,
entrySuffixes: readonly string[],
barrels: readonly BarrelPattern[],
isAllowed: (key: string) => boolean,
): ExportEntry[] {
const graph = buildImportGraph(closure);
const entrypoints = findEntrypoints(closure, entrySuffixes, barrels);
const reachable = new Set([
...walkReachable(entrypoints, graph).reachableExports,
...externalReach(closure, graph),
]);
return closure.exports.filter((e) => {
const key = `${e.file}::${e.name}`;
return !reachable.has(key) && !isAllowed(key);
});
};