mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
ccba355fa9
Share embedded-map version admission and derive indexed overlap from represented mappings. Carry createRequire filename bases through worker facts and graph identity. Select factory export bindings at each bundler owner. Validated accepted and malformed source maps through real CLI and stdio MCP codec workflows; verified loader and esbuild graph output against Node runtime controls. Rebased consumer checks passed. Fixes #6803 Fixes #6614 Fixes #6798 Fixes #6797
264 lines
8.4 KiB
TypeScript
264 lines
8.4 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
|
|
import * as t from "@babel/types";
|
|
|
|
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
|
|
|
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
|
import { semanticStaticPropertyKey } from "./javascriptAstValues.js";
|
|
import { stringValue } from "./javascriptStaticAnalysisHelpers.js";
|
|
import {
|
|
resolveSemanticBindingState,
|
|
type JavaScriptSemanticAnalysisState,
|
|
} from "./javascriptSemanticState.js";
|
|
|
|
/** Static CommonJS export names. */
|
|
export interface StaticExports {
|
|
readonly values: readonly string[];
|
|
}
|
|
|
|
/** Derive a rename-resistant syntax fingerprint without code execution. */
|
|
export const fingerprintJavaScriptAst = (node: t.Node): string => {
|
|
const tokens: string[] = [];
|
|
traverseJavaScriptAst(node, {
|
|
enter: (current) => {
|
|
tokens.push(...semanticTokens(current));
|
|
return undefined;
|
|
},
|
|
});
|
|
return createHash("sha256").update(JSON.stringify(tokens)).digest("hex");
|
|
};
|
|
|
|
/** Collect statically declared CommonJS/bundler export names from one factory. */
|
|
export const collectJavaScriptExports = (
|
|
node: t.Node,
|
|
bindings?: JavaScriptSemanticAnalysisState,
|
|
exportsParameter?: t.Identifier,
|
|
): StaticExports => {
|
|
const exports = new Set<string>();
|
|
const factoryExports =
|
|
bindings !== undefined &&
|
|
t.isFunction(node) &&
|
|
exportsParameter !== undefined
|
|
? resolveSemanticBindingState(
|
|
bindings,
|
|
exportsParameter,
|
|
exportsParameter.name,
|
|
)?.bindingId
|
|
: undefined;
|
|
const isExportsAlias = (reference: t.Node | null): boolean => {
|
|
if (!t.isIdentifier(reference)) return false;
|
|
if (factoryExports === undefined) return reference.name === "exports";
|
|
return (
|
|
bindings !== undefined &&
|
|
resolveSemanticBindingState(bindings, reference, reference.name)
|
|
?.bindingId === factoryExports
|
|
);
|
|
};
|
|
traverseJavaScriptAst(node, {
|
|
enter: (current) => {
|
|
if (t.isAssignmentExpression(current))
|
|
collectAssignmentExports(
|
|
current.left,
|
|
current.right,
|
|
exports,
|
|
isExportsAlias,
|
|
);
|
|
if (t.isCallExpression(current))
|
|
collectCallExports(current, exports, isExportsAlias);
|
|
return undefined;
|
|
},
|
|
});
|
|
return {
|
|
values: [...exports].sort(compareUnicodeCodePoints),
|
|
};
|
|
};
|
|
|
|
const semanticTokens = (node: t.Node): string[] => {
|
|
const tokens = [`node:${node.type}`];
|
|
if (t.isStringLiteral(node)) tokens.push(`string:${node.value}`);
|
|
else if (t.isBigIntLiteral(node)) tokens.push(`bigint:${String(node.value)}`);
|
|
else if (t.isDirectiveLiteral(node)) tokens.push(`directive:${node.value}`);
|
|
else if (t.isNumericLiteral(node))
|
|
tokens.push(`number:${String(node.value)}`);
|
|
else if (t.isBooleanLiteral(node))
|
|
tokens.push(`boolean:${String(node.value)}`);
|
|
else if (t.isRegExpLiteral(node))
|
|
tokens.push(`regexp:${node.pattern}/${node.flags}`);
|
|
else if (t.isTemplateElement(node))
|
|
tokens.push(`template:${JSON.stringify(node.value)}`);
|
|
else if (
|
|
t.isBinaryExpression(node) ||
|
|
t.isLogicalExpression(node) ||
|
|
t.isUnaryExpression(node) ||
|
|
t.isUpdateExpression(node) ||
|
|
t.isAssignmentExpression(node)
|
|
)
|
|
tokens.push(`operator:${node.operator}`);
|
|
if (t.isUpdateExpression(node)) tokens.push(`prefix:${String(node.prefix)}`);
|
|
if (t.isVariableDeclaration(node)) tokens.push(`kind:${node.kind}`);
|
|
if (t.isFunction(node)) {
|
|
tokens.push(`async:${String(node.async)}`);
|
|
tokens.push(`generator:${String(node.generator)}`);
|
|
}
|
|
if (t.isObjectMethod(node)) tokens.push(`kind:${node.kind}`);
|
|
if (t.isClassMethod(node) || t.isClassPrivateMethod(node)) {
|
|
tokens.push(`kind:${node.kind}`);
|
|
tokens.push(`static:${String(node.static)}`);
|
|
}
|
|
if (
|
|
t.isClassProperty(node) ||
|
|
t.isClassPrivateProperty(node) ||
|
|
t.isClassAccessorProperty(node)
|
|
)
|
|
tokens.push(`static:${String(node.static)}`);
|
|
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
// A computed identifier names a local binding, not a fixed member.
|
|
tokens.push(`computed:${String(node.computed)}`);
|
|
if (!node.computed) {
|
|
const property = semanticStaticPropertyKey(node.property, false);
|
|
if (property !== null) tokens.push(`property:${property}`);
|
|
}
|
|
}
|
|
if (
|
|
t.isObjectProperty(node) ||
|
|
t.isObjectMethod(node) ||
|
|
t.isClassMethod(node) ||
|
|
t.isClassProperty(node) ||
|
|
t.isClassAccessorProperty(node)
|
|
) {
|
|
tokens.push(`computed:${String(node.computed)}`);
|
|
if (!node.computed) {
|
|
const key = semanticStaticPropertyKey(node.key, false);
|
|
if (key !== null) tokens.push(`key:${key}`);
|
|
}
|
|
}
|
|
return tokens;
|
|
};
|
|
|
|
const collectAssignmentExports = (
|
|
left: t.LVal | t.OptionalMemberExpression,
|
|
right: t.Expression,
|
|
output: Set<string>,
|
|
isExportsAlias: (root: t.Node | null) => boolean,
|
|
): void => {
|
|
const path = memberPath(left);
|
|
if (path === null) return;
|
|
const root = memberRoot(left);
|
|
const exportsAlias = isExportsAlias(root);
|
|
// Rebinding the CommonJS alias does not replace module.exports.
|
|
if (path.length === 1 && exportsAlias) return;
|
|
const exportOffset = exportsAlias
|
|
? 1
|
|
: path[0] === "module" && path[1] === "exports"
|
|
? 2
|
|
: null;
|
|
if (exportOffset === null) return;
|
|
if (path.length === exportOffset) {
|
|
if (t.isObjectExpression(right)) collectObjectKeys(right, output);
|
|
else addExport(output, "default");
|
|
return;
|
|
}
|
|
// A CommonJS export assignment exposes the first property below the
|
|
// exports object. Deeper writes mutate that exported value, rather than
|
|
// creating an export whose name is the dotted member path.
|
|
addExport(output, path[exportOffset]!);
|
|
};
|
|
|
|
const collectCallExports = (
|
|
call: t.CallExpression,
|
|
output: Set<string>,
|
|
isExportsAlias: (root: t.Node | null) => boolean,
|
|
): void => {
|
|
const callee = memberPath(call.callee);
|
|
if (callee === null) return;
|
|
const definePropertyCall =
|
|
callee.length === 2 &&
|
|
callee[0] === "Object" &&
|
|
callee[1] === "defineProperty";
|
|
const declarationCall = callee.length >= 2 && callee.at(-1) === "d";
|
|
if (!definePropertyCall && !declarationCall) return;
|
|
const targetNode = call.arguments[0];
|
|
const target = memberPath(targetNode);
|
|
const exportTarget =
|
|
isExportsAlias(targetNode ?? null) ||
|
|
(target !== null &&
|
|
target.length === 2 &&
|
|
target[0] === "module" &&
|
|
target[1] === "exports");
|
|
if (!exportTarget) return;
|
|
if (definePropertyCall) {
|
|
const name = stringValue(call.arguments[1]);
|
|
if (name !== undefined) addExport(output, name);
|
|
return;
|
|
}
|
|
const declarations = call.arguments[1];
|
|
if (t.isObjectExpression(declarations))
|
|
collectObjectKeys(declarations, output);
|
|
};
|
|
|
|
const memberRoot = (node: t.Node | null | undefined): t.Node | null => {
|
|
let current = node;
|
|
while (
|
|
current !== null &&
|
|
current !== undefined &&
|
|
(t.isMemberExpression(current) || t.isOptionalMemberExpression(current))
|
|
)
|
|
current = current.object;
|
|
return current ?? null;
|
|
};
|
|
|
|
const collectObjectKeys = (
|
|
object: t.ObjectExpression,
|
|
output: Set<string>,
|
|
): void => {
|
|
for (const property of object.properties) {
|
|
if (!t.isObjectProperty(property) && !t.isObjectMethod(property)) continue;
|
|
const name = semanticStaticPropertyKey(property.key, property.computed);
|
|
// In an object literal, a non-computed `__proto__: value` sets the
|
|
// prototype instead of creating an own property. Shorthand, computed,
|
|
// and method forms do create own properties.
|
|
if (
|
|
name !== null &&
|
|
!(
|
|
t.isObjectProperty(property) &&
|
|
!property.computed &&
|
|
!property.shorthand &&
|
|
name === "__proto__"
|
|
)
|
|
)
|
|
addExport(output, name);
|
|
}
|
|
};
|
|
|
|
const addExport = (output: Set<string>, name: string): void => {
|
|
output.add(name);
|
|
};
|
|
|
|
const memberPath = (
|
|
node: t.Node | null | undefined,
|
|
): readonly string[] | null => {
|
|
const properties: string[] = [];
|
|
let current = node;
|
|
while (
|
|
current !== undefined &&
|
|
current !== null &&
|
|
(t.isMemberExpression(current) || t.isOptionalMemberExpression(current))
|
|
) {
|
|
const property = semanticStaticPropertyKey(
|
|
current.property,
|
|
current.computed,
|
|
);
|
|
if (property === null) return null;
|
|
properties.push(property);
|
|
current = current.object;
|
|
}
|
|
if (current === undefined || current === null) return null;
|
|
const root = t.isIdentifier(current)
|
|
? current.name
|
|
: t.isThisExpression(current)
|
|
? "this"
|
|
: null;
|
|
return root === null ? null : [root, ...properties.reverse()];
|
|
};
|