shared/analyzers/taxonomy.tree.analyzer.ts
shared/analyzers/taxonomy.tree.analyzer.ts is a file in GovLab Extension Host. 132 lines of code and 37 definitions.
import type { PlacementFinding, TaxonomyFinding, TaxonomyReport } from "../../types/taxonomy.types.ts";
import {
containersFor,
isDeclaredContainer,
isForeignContainer,
isGeneratedFolder,
isIgnoredName,
isNameExempt,
isNestedRoot,
isSpecialContainer,
vocabularyFor,
} from "../manifests/taxonomy.manifest.ts";
import { existsSync, readdirSync, statSync } from "node:fs";
import { fixtureMarkerOf, isTestRoot, taxonomyRoots, testMarkerOf } from "../manifests/taxonomy.root.manifest.ts";
import { namingFinding, placementFinding, testFinding } from "./taxonomy.analyzer.ts";
import { WORKSPACE_ROOT } from "../resolvers/anchor.resolver.ts";
import { join } from "node:path";
const PATH_SEPARATOR = "/";
interface Children {
dirs: string[];
files: string[];
}
const childrenOf = function childrenOf(relDir: string, root?: string): Children {
const absDir = join(WORKSPACE_ROOT, relDir);
const named = readdirSync(absDir)
.filter((name) => !isIgnoredName(name, root))
.toSorted((a, b) => a.localeCompare(b));
const dirs = named.filter((name) => statSync(join(absDir, name)).isDirectory());
return { dirs, files: named.filter((name) => !dirs.includes(name)) };
};
const filesUnder = function filesUnder(relDir: string, root?: string): string[] {
const { dirs, files } = childrenOf(relDir, root);
const own = files.map((name) => `${relDir}${PATH_SEPARATOR}${name}`);
return [...own, ...dirs.flatMap((name) => filesUnder(`${relDir}${PATH_SEPARATOR}${name}`, root))];
};
const carriesMarker = function carriesMarker(basename: string, marker: string, separator: string): boolean {
const segments = basename.split(separator);
return segments.length > 2 && segments.at(-2) === marker;
};
export const generatedFolderFindings = function generatedFolderFindings(
relFolder: string,
root?: string,
): TaxonomyFinding[] {
const vocabulary = vocabularyFor(root);
const marker = vocabulary.generatedFolder?.marker ?? "";
return filesUnder(relFolder, root)
.filter(
(path) => !carriesMarker(path.slice(path.lastIndexOf(PATH_SEPARATOR) + 1), marker, vocabulary.separator),
)
.map((path): TaxonomyFinding => ({
data: { folder: relFolder, marker },
messageId: "generatedFolderIntruder",
path,
}));
};
const at = function at(path: string, finding: PlacementFinding | null): TaxonomyFinding[] {
return finding === null ? [] : [{ ...finding, path }];
};
const fileFindings = function fileFindings(path: string, root: string, special: boolean): TaxonomyFinding[] {
const below = path.slice(root.length + 1).split(PATH_SEPARATOR);
const basename = below.at(-1) ?? "";
const folders = below.slice(0, -1);
if (isTestRoot(root) || testMarkerOf(basename) !== undefined || fixtureMarkerOf(basename, root) !== undefined) {
return at(path, testFinding(basename, folders, root));
}
const placement = special ? null : placementFinding(basename, folders, root);
return [...at(path, placement), ...at(path, namingFinding(basename, folders, root))];
};
const bucketFindings = function bucketFindings(root: string, container: string, files: string[]): TaxonomyFinding[] {
const relContainer = `${root}${PATH_SEPARATOR}${container}`;
const nested = childrenOf(relContainer, root).dirs.map((name): TaxonomyFinding => ({
data: { container, nested: name, root },
messageId: "nestedInSpecial",
path: `${relContainer}${PATH_SEPARATOR}${name}`,
}));
return [...nested, ...files.flatMap((path) => fileFindings(path, root, true))];
};
const containerFindings = function containerFindings(
root: string,
container: string,
files: string[],
): TaxonomyFinding[] {
if (!isDeclaredContainer(root, container)) {
const data = { container, declared: containersFor(root).join(", "), root };
return [{ data, messageId: "undeclaredContainer", path: `${root}${PATH_SEPARATOR}${container}` }];
}
if (isSpecialContainer(root, container)) {
const direct = files.filter(
(path) => path.split(PATH_SEPARATOR).length === root.split(PATH_SEPARATOR).length + 2,
);
return bucketFindings(root, container, direct);
}
return files.flatMap((path) => fileFindings(path, root, false));
};
const rootReport = function rootReport(root: string): TaxonomyReport {
if (!existsSync(join(WORKSPACE_ROOT, root))) {
return { assessed: 0, findings: [{ data: { root }, messageId: "missingRoot", path: root }] };
}
const children = childrenOf(root, root);
const outputs = children.dirs.filter((name) => isGeneratedFolder(name, root));
const dirs = children.dirs.filter(
(name) => !isNestedRoot(root, name) && !isForeignContainer(root, name) && !outputs.includes(name),
);
const missing = containersFor(root)
.filter((name) => !dirs.includes(name) && !isTestRoot(root))
.map((container): TaxonomyFinding => ({
data: { container, root },
messageId: "missingContainer",
path: root,
}));
const loose = children.files
.filter((name) => !isNameExempt(name, root))
.map((name): TaxonomyFinding => ({
data: { name, root },
messageId: "looseFileAtRoot",
path: `${root}${PATH_SEPARATOR}${name}`,
}));
const perContainer = dirs.map((name) => filesUnder(`${root}${PATH_SEPARATOR}${name}`, root));
const findings = dirs.flatMap((name, index) => containerFindings(root, name, perContainer[index] ?? []));
const generated = outputs.flatMap((name) => generatedFolderFindings(`${root}${PATH_SEPARATOR}${name}`, root));
const assessed = loose.length + perContainer.reduce((sum, files) => sum + files.length, 0);
return { assessed, findings: [...missing, ...loose, ...findings, ...generated] };
};
export const taxonomyReport = function taxonomyReport(): TaxonomyReport {
const reports = taxonomyRoots()
.toSorted((a, b) => a.localeCompare(b))
.map(rootReport);
return {
assessed: reports.reduce((sum, report) => sum + report.assessed, 0),
findings: reports.flatMap((report) => report.findings),
};
};