Files
rea/src/domain/javascript/electronStaticAnalysisNative.ts
T
morluto 765e809e36 refactor: use canonical implementations and types directly
Remove forwarding wrappers and shadow aliases, migrate callers and verification scripts to their owners, and share one exhaustive semantic resource-bound projection.
2026-10-09 23:35:19 +08:00

217 lines
6.3 KiB
TypeScript

import * as t from "@babel/types";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { stripQueryAndFragment } from "../artifactPathSyntax.js";
import type { ElectronNativeAddonBindingFinding } from "./electronStaticAnalysisTypes.js";
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
import {
argumentNode,
calleeName,
range,
stringValue,
} from "./javascriptStaticAnalysisHelpers.js";
import {
propertyName,
semanticStaticPropertyName,
} from "./javascriptAstValues.js";
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
interface NativeBindingInput {
readonly context: JavaScriptFindingContext;
readonly node: t.Node;
readonly specifier: string;
readonly kind: ElectronNativeAddonBindingFinding["binding_kind"];
readonly moduleKind: ElectronNativeAddonBindingFinding["module_kind"];
readonly members: readonly string[];
}
/** Inspect JavaScript-side imports and re-exports of native .node addons. */
export const inspectElectronNativeNode = (
node: t.Node,
context: JavaScriptFindingContext,
): void => {
if (t.isImportDeclaration(node)) inspectImport(node, context);
else if (t.isExportNamedDeclaration(node)) inspectNamedExport(node, context);
else if (t.isExportAllDeclaration(node)) inspectExportAll(node, context);
else if (t.isVariableDeclarator(node)) inspectRequireBinding(node, context);
else if (t.isAssignmentExpression(node)) inspectReExport(node, context);
};
const inspectImport = (
node: t.ImportDeclaration,
context: JavaScriptFindingContext,
): void => {
if (!isNativeSpecifier(node.source.value, "import")) return;
const members = node.specifiers.map((specifier) => {
if (t.isImportDefaultSpecifier(specifier)) return "default";
if (t.isImportNamespaceSpecifier(specifier)) return "*";
return propertyName(specifier.imported) || "[dynamic-import]";
});
addBinding({
context,
node,
specifier: node.source.value,
kind: "import",
moduleKind: "import",
members,
});
};
const inspectNamedExport = (
node: t.ExportNamedDeclaration,
context: JavaScriptFindingContext,
): void => {
if (
node.source === null ||
node.source === undefined ||
!isNativeSpecifier(node.source.value, "import")
)
return;
const members = node.specifiers.map((specifier) => {
if (t.isExportSpecifier(specifier)) return propertyName(specifier.local);
return "*";
});
addBinding({
context,
node,
specifier: node.source.value,
kind: "re-export",
moduleKind: "import",
members,
});
};
const inspectExportAll = (
node: t.ExportAllDeclaration,
context: JavaScriptFindingContext,
): void => {
if (
node.source === undefined ||
!isNativeSpecifier(node.source.value, "import")
)
return;
addBinding({
context,
node,
specifier: node.source.value,
kind: "re-export",
moduleKind: "import",
members: ["*"],
});
};
const inspectRequireBinding = (
node: t.VariableDeclarator,
context: JavaScriptFindingContext,
): void => {
const required = nativeRequire(node.init);
if (required === undefined) return;
addBinding({
context,
node,
specifier: required.specifier,
kind: "require",
moduleKind: "require",
members:
required.member === null ? bindingMembers(node.id) : [required.member],
});
};
const inspectReExport = (
node: t.AssignmentExpression,
context: JavaScriptFindingContext,
): void => {
const required = nativeRequire(node.right);
if (required === undefined || !isModuleExport(node.left)) return;
addBinding({
context,
node,
specifier: required.specifier,
kind: "re-export",
moduleKind: "require",
members: [required.member ?? exportedMember(node.left) ?? "*"],
});
};
const addBinding = (input: NativeBindingInput): void => {
const { context, node, specifier, kind } = input;
const unique = [
...new Set(input.members.filter((member) => member !== "")),
].sort(compareUnicodeCodePoints);
const members = unique.length === 0 ? ["*"] : unique;
addLocatedFinding(context, {
collection: context.accumulator.nativeAddonBindings,
key: `native-addon-binding\0${kind}\0${specifier}\0${members.join("\0")}`,
node,
value: {
specifier,
binding_kind: kind,
module_kind: input.moduleKind,
members,
module_key: null,
location: range(node),
},
});
};
const nativeRequire = (
node: t.Node | null | undefined,
):
| { readonly specifier: string; readonly member: string | null }
| undefined => {
let member: string | null = null;
while (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
if (!t.isNode(node.object)) return undefined;
const property = semanticStaticPropertyName(node.property, node.computed);
if (member === null && property !== "") member = property;
node = node.object;
}
if (!t.isCallExpression(node)) return undefined;
const name = calleeName(node.callee);
if (
name !== "require" &&
!name.endsWith(".require") &&
!name.includes("__webpack_require__")
)
return undefined;
const specifier = stringValue(argumentNode(node.arguments[0]));
return specifier !== undefined && isNativeSpecifier(specifier, "require")
? { specifier, member }
: undefined;
};
const bindingMembers = (pattern: t.Node): string[] => {
if (t.isIdentifier(pattern)) return ["*"];
if (!t.isObjectPattern(pattern)) return ["*"];
return pattern.properties.map((property) => {
if (t.isRestElement(property)) return "*";
return propertyName(property.key) || "[dynamic-import]";
});
};
const isModuleExport = (node: t.Node): boolean => {
const name = calleeName(node);
return (
name === "module.exports" ||
name.startsWith("module.exports.") ||
name.startsWith("exports.")
);
};
const exportedMember = (node: t.Node): string | undefined => {
const name = calleeName(node);
if (name.startsWith("module.exports."))
return name.slice("module.exports.".length);
if (name.startsWith("exports.")) return name.slice("exports.".length);
return undefined;
};
const isNativeSpecifier = (
specifier: string,
moduleKind: ElectronNativeAddonBindingFinding["module_kind"],
): boolean => {
const path =
moduleKind === "require" ? specifier : stripQueryAndFragment(specifier);
return path.toLowerCase().endsWith(".node");
};