mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
143 lines
4.3 KiB
JavaScript
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;
|
|
};
|