Files
rea/scripts/lib/module-boundaries.mjs
T

143 lines
4.3 KiB
JavaScript

import { parse } from "@babel/parser";
import { traverseFast } from "@babel/types";
import { isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const PURE_LAYERS = new Set(["domain", "contracts"]);
const MIGRATED_PROVIDER_ROOTS = new Set([
"android",
"firmware",
"ghidra",
"hopper",
"ida",
"inspector",
"javascript",
]);
const PROVIDER_ROOTS = new Set([
...MIGRATED_PROVIDER_ROOTS,
"artifacts",
"browser",
"dotnet",
"native",
]);
const repositoryPath = (root, path) =>
relative(root, path).replaceAll("\\", "/");
const literalValue = (node) => {
if (node?.type === "StringLiteral") return node.value;
if (node?.type === "TemplateLiteral" && node.expressions.length === 0)
return node.quasis[0]?.value.cooked ?? undefined;
return undefined;
};
const importSpecifier = (node) => {
switch (node.type) {
case "ImportDeclaration":
case "ExportNamedDeclaration":
case "ExportAllDeclaration":
case "ImportExpression":
case "TSImportType":
return literalValue(node.source);
case "TSImportEqualsDeclaration":
return node.moduleReference.type === "TSExternalModuleReference"
? literalValue(node.moduleReference.expression)
: undefined;
default:
return undefined;
}
};
const sourceTarget = (file, specifier) => {
if (specifier.startsWith("file:")) return fileURLToPath(specifier);
if (isAbsolute(specifier)) return resolve(specifier);
return specifier.startsWith(".")
? fileURLToPath(new URL(specifier, pathToFileURL(file)))
: undefined;
};
const failedBoundary = (file, target) => {
if (!file.startsWith("src/") || !target.startsWith("src/")) return undefined;
const layer = file.split("/")[1];
const targetLayer = target.split("/")[1];
if (
file.startsWith("src/process/capture/") &&
(["application", "composition", "server", "cli", "main"].includes(
targetLayer,
) ||
/^src\/(?:cli|main)\./u.test(target))
)
return "process-capture";
if (
file.startsWith("src/application/javascript/") &&
/^src\/artifacts\/(?:Asar|Directory)ArtifactReader\.(?:js|ts)$/u.test(
target,
)
)
return "provider-construction";
if (
(file.startsWith("src/artifacts/javascript/") ||
file.startsWith("src/artifacts/apple/") ||
file.startsWith("src/artifacts/inventory/") ||
file.startsWith("src/artifacts/extraction/") ||
/^src\/artifacts\/ArtifactHash\.(?:js|ts)$/u.test(file)) &&
(["application", "composition", "server", "cli", "main"].includes(
targetLayer,
) ||
/^src\/(?:cli|main)\./u.test(target))
)
return "artifact-acquisition";
if (
PROVIDER_ROOTS.has(layer) &&
/^src\/generatedMcpToolCatalog\.(?:js|ts)$/u.test(target)
)
return "provider-generated-catalog";
if (PURE_LAYERS.has(layer)) {
if (
(/^src\/[^/]+\//u.test(target) && !PURE_LAYERS.has(targetLayer)) ||
/^src\/(?:cli|main)\./u.test(target)
)
return "pure-layer";
}
if (layer === "application" && targetLayer === "composition")
return "application-composition";
if (
(layer === "application" || layer === "server") &&
(MIGRATED_PROVIDER_ROOTS.has(targetLayer) ||
/^src\/(?:artifacts\/ArtifactProvider|dotnet\/ManagedStaticProvider|native\/NativeMacOSProvider)\.[^/]+$/u.test(
target,
) ||
/^src\/browser\/[^/]*Provider\.[^/]+$/u.test(target))
)
return "provider-construction";
return undefined;
};
/** Check resolved source ownership, including type imports and reexports. */
export const inspectModuleBoundaries = (file, source, root) => {
const absoluteFile = resolve(root, file);
const importer = repositoryPath(root, absoluteFile);
const tree = parse(source, {
sourceType: "module",
plugins: ["typescript"],
});
const violations = [];
traverseFast(tree, (node) => {
const specifier = importSpecifier(node);
if (specifier === undefined) return;
const resolved = sourceTarget(absoluteFile, specifier);
if (resolved === undefined) return;
const target = repositoryPath(root, resolved);
const boundary = failedBoundary(importer, target);
if (boundary !== undefined)
violations.push({
file: importer,
line: node.loc?.start.line ?? 1,
specifier,
target,
boundary,
});
});
return violations;
};