mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
fix(javascript): omit self-referential import edges with disclosure (#1130)
* fix(javascript): omit self-referential import edges with disclosure A specifier that resolves back to the importing module itself produced an imports edge from a module to itself, which the application graph schema forbids; one such specifier failed result validation and lost the whole analysis. Skip those edges at composition time and disclose the omission count in the graph limitations. Fixes #1129 * test(javascript): verify self-import behavior through the compiled CLI --------- Co-authored-by: BaaRai <baarai@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
3 files changed
+180
-7
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
import { addJavaScriptStaticFindings } from "./JavaScriptArtifactGraphFindings.js";
|
||||
import {
|
||||
addJavaScriptModuleRelationships,
|
||||
type JavaScriptModuleRelationshipOmissions,
|
||||
addJavaScriptSourceModules,
|
||||
} from "./JavaScriptModuleRelationships.js";
|
||||
import {
|
||||
@@ -88,7 +89,7 @@ const buildJavaScriptArtifactGraphInput = (
|
||||
const packageRoots = addJavaScriptPackageNodes(context);
|
||||
addJavaScriptSourceModules(context);
|
||||
addJavaScriptBundlerNodes(context);
|
||||
addJavaScriptModuleRelationships(context);
|
||||
const relationshipOmissions = addJavaScriptModuleRelationships(context);
|
||||
addJavaScriptStaticFindings(context);
|
||||
addElectronBoundaries(context);
|
||||
addJavaScriptHtmlRoles(context);
|
||||
@@ -103,7 +104,11 @@ const buildJavaScriptArtifactGraphInput = (
|
||||
nodes: accumulator.nodes(),
|
||||
edges: accumulator.edges(),
|
||||
coverage,
|
||||
limitations: graphLimitations(context, coverage.status),
|
||||
limitations: graphLimitations(
|
||||
context,
|
||||
coverage.status,
|
||||
relationshipOmissions,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -171,6 +176,7 @@ const applicationGraphResourceLimit = (
|
||||
const graphLimitations = (
|
||||
context: JavaScriptArtifactGraphContext,
|
||||
coverage: "complete" | "partial" | "unknown" | "unavailable",
|
||||
relationshipOmissions: JavaScriptModuleRelationshipOmissions,
|
||||
): string[] => {
|
||||
const ipc = collectElectronIpcRecords(context.analysis);
|
||||
const pairings = classifyElectronIpcPairings(ipc);
|
||||
@@ -189,6 +195,11 @@ const graphLimitations = (
|
||||
);
|
||||
return [
|
||||
...context.analysis.limitations,
|
||||
...(relationshipOmissions.selfImports > 0
|
||||
? [
|
||||
`${String(relationshipOmissions.selfImports)} import ${relationshipOmissions.selfImports === 1 ? "specifier" : "specifiers"} resolved back to the importing module itself and ${relationshipOmissions.selfImports === 1 ? "was" : "were"} omitted; application graph edges cannot be self-referential.`,
|
||||
]
|
||||
: []),
|
||||
"CommonJS and ESM binding relationships were recovered from inert syntax and resolved only within the inventoried artifact container.",
|
||||
"Webpack/Rspack factories were recovered from AST literals; REA did not invoke push handlers or bundle bootstrap code.",
|
||||
"Static imports, entrypoints, workers, endpoints, and storage relationships do not prove runtime execution.",
|
||||
|
||||
@@ -129,27 +129,40 @@ export const addJavaScriptSourceModules = (
|
||||
}
|
||||
};
|
||||
|
||||
/** Relationship compositions the application graph cannot represent, for disclosure. */
|
||||
export interface JavaScriptModuleRelationshipOmissions {
|
||||
/** Import specifiers that resolved back to the importing module itself. */
|
||||
readonly selfImports: number;
|
||||
}
|
||||
|
||||
type RelationshipOmissionCounter = { selfImports: number };
|
||||
|
||||
/** Compose bounded CommonJS and ESM binding relationships across artifact files. */
|
||||
export const addJavaScriptModuleRelationships = (
|
||||
context: JavaScriptArtifactGraphContext,
|
||||
): void => {
|
||||
): JavaScriptModuleRelationshipOmissions => {
|
||||
const omissions = { selfImports: 0 };
|
||||
for (const analyzed of context.analysis.files) {
|
||||
const { file, semantic } = analyzed;
|
||||
const source = context.sourceModuleNodes.get(file.path);
|
||||
if (semantic === null || source === undefined) continue;
|
||||
for (const link of semantic.ir.moduleLinks) {
|
||||
const input = { context, file, semantic, source, link };
|
||||
if (isExportLink(link)) addExportRelationship(input);
|
||||
if (isExportLink(link)) addExportRelationship(input, omissions);
|
||||
else if (link.specifier !== null)
|
||||
addImportRelationship(input, source, {
|
||||
addImportRelationship(input, source, omissions, {
|
||||
specifier: link.specifier,
|
||||
importedPath: link.importedName === null ? [] : [link.importedName],
|
||||
});
|
||||
}
|
||||
}
|
||||
return omissions;
|
||||
};
|
||||
|
||||
const addExportRelationship = (input: RelationshipInput): void => {
|
||||
const addExportRelationship = (
|
||||
input: RelationshipInput,
|
||||
omissions: RelationshipOmissionCounter,
|
||||
): void => {
|
||||
const { context, file, semantic, source, link } = input;
|
||||
if (link.exportedName === null) return;
|
||||
const baseCoverage = semanticCoverage(semantic);
|
||||
@@ -213,15 +226,25 @@ const addExportRelationship = (input: RelationshipInput): void => {
|
||||
evidence: relationshipEvidence(input, "expose-module-export", []),
|
||||
});
|
||||
const origin = moduleOriginForExport(semantic.ir, link);
|
||||
if (origin !== null) addImportRelationship(input, exported, origin);
|
||||
if (origin !== null)
|
||||
addImportRelationship(input, exported, omissions, origin);
|
||||
};
|
||||
|
||||
const addImportRelationship = (
|
||||
input: RelationshipInput,
|
||||
source: ApplicationNode,
|
||||
omissions: RelationshipOmissionCounter,
|
||||
origin: JavaScriptModuleOrigin,
|
||||
): void => {
|
||||
const target = resolveModuleTarget(input, origin.specifier);
|
||||
// A specifier that resolves back to the importing module itself (a literal
|
||||
// self-import, self-require, or artifact-confined path alias)
|
||||
// cannot become an edge: the application graph forbids self-referential
|
||||
// edges, and emitting one fails result validation for the whole analysis.
|
||||
if (target.node.node_id === source.node_id) {
|
||||
omissions.selfImports += 1;
|
||||
return;
|
||||
}
|
||||
input.context.accumulator.addEdge({
|
||||
source_node_id: source.node_id,
|
||||
target_node_id: target.node.node_id,
|
||||
|
||||
Reference in new issue
Block a user