* fix(javascript): exclude overwritten Electron API identities * test(javascript): bind Electron in file-base path fixture * test(electron): bind BrowserWindow in preload override fixtures * test(mcp): allow cold catalog validation in runtime probe * test(electron): bind APIs in option reconstruction fixtures
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
import * as t from "@babel/types";
|
|
|
|
import {
|
|
semanticStaticPropertyKey,
|
|
semanticMemberWriteTargets,
|
|
unwrapJavaScriptExpression,
|
|
} from "./javascriptAstValues.js";
|
|
import { SemanticPropertyPathCoverage } from "./javascriptSemanticPropertyPaths.js";
|
|
import { traverseJavaScriptAstSteps } from "./javascriptSemanticTraversal.js";
|
|
|
|
/** Match Electron's supported module entry points. */
|
|
export const ELECTRON_MODULE =
|
|
/^electron(?:\/(?:common|main|renderer|utility))?$/u;
|
|
|
|
/** Static identity recovery cannot establish the absence of Electron usage. */
|
|
export const ELECTRON_IDENTITY_LIMITATION =
|
|
"Electron API identity follows proven lexical module aliases. Reassigned bindings, dynamic selections, and visibly overwritten namespace members are unresolved; missing findings do not establish absence. Arbitrary call side effects and runtime registration are not evaluated.";
|
|
|
|
/** Preserve exact property segments instead of flattening literal dotted keys. */
|
|
export const electronMemberPath = (
|
|
node: t.Node,
|
|
): {
|
|
readonly root: t.Node;
|
|
readonly members: readonly (string | null)[];
|
|
} => {
|
|
let root = unwrapJavaScriptExpression(node).node;
|
|
const members: (string | null)[] = [];
|
|
while (t.isMemberExpression(root) || t.isOptionalMemberExpression(root)) {
|
|
members.push(semanticStaticPropertyKey(root.property, root.computed));
|
|
root = unwrapJavaScriptExpression(root.object).node;
|
|
}
|
|
return { root, members: members.toReversed() };
|
|
};
|
|
|
|
/** Index visible writes through proven Electron aliases, without executing them. */
|
|
export function* collectElectronMemberWritesSteps(
|
|
program: t.Program,
|
|
origin: (root: t.Node) => readonly string[] | undefined,
|
|
): Generator<void, SemanticPropertyPathCoverage> {
|
|
const writes = new SemanticPropertyPathCoverage();
|
|
yield* traverseJavaScriptAstSteps(program, {
|
|
enter: (node) => {
|
|
const target = t.isAssignmentExpression(node)
|
|
? node.left
|
|
: t.isUpdateExpression(node)
|
|
? node.argument
|
|
: t.isUnaryExpression(node, { operator: "delete" })
|
|
? node.argument
|
|
: t.isForOfStatement(node) || t.isForInStatement(node)
|
|
? node.left
|
|
: undefined;
|
|
if (target === undefined) return;
|
|
for (const member of semanticMemberWriteTargets(target)) {
|
|
const { root, members } = electronMemberPath(member);
|
|
const exported = origin(root);
|
|
if (exported !== undefined) writes.retain([...exported, ...members]);
|
|
}
|
|
},
|
|
});
|
|
return writes;
|
|
}
|