mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
fix(javascript): preserve builtin identity and share alias traversal
Track module writes and escapes at their owner, preserve capture and reassignment ordering, and share CommonJS/default namespace identity without erasing parent-slot changes. Carry write sites into ambient alias resolution and use parsed write targets across consumers. Deduplicate returned-reference work with cycle context; retain unchanged temporal selection identity so provenance memoization stays effective. Reviewed fixes include #2041, #2465, #2830, #2881, and #2948. Remove superseded mutation representations and the ineffective per-markValue dedup layer. Validation: 380 focused JavaScript tests passed under a 384 MiB heap cap, including the previously aborting 30-level/128-call alias regression. Six public CLI/MCP builtin tests passed. Exact pdf.js completed-workflow performance remains unmeasured.
This commit is contained in:
@@ -27,6 +27,10 @@ import { range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import {
|
||||
propertyName,
|
||||
semanticStaticPropertyKey,
|
||||
semanticWrappedWriteSite,
|
||||
semanticWriteSiteReadsFirst,
|
||||
semanticWriteSiteTarget,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import {
|
||||
isTypeOnlyExportDeclaration,
|
||||
@@ -175,12 +179,17 @@ export function* collectSemanticReferencesSteps(
|
||||
!patterns.some((pattern) => bindsIdentifier(pattern, parent, node)))
|
||||
)
|
||||
return;
|
||||
const role = semanticIdentifierRole(node, parent, readAncestors());
|
||||
const ancestors = readAncestors();
|
||||
const role = semanticIdentifierRole(node, parent, ancestors);
|
||||
if (role === null) return;
|
||||
// A compound assignment or update reads before it writes; emit both so
|
||||
// reference consumers see the read they actually execute.
|
||||
const roles =
|
||||
role === "write" && semanticReadsBeforeWrite(node, parent)
|
||||
role === "write" &&
|
||||
semanticReadsBeforeWrite(
|
||||
node,
|
||||
semanticWrappedWriteSite(node, parent, ancestors) ?? parent,
|
||||
)
|
||||
? (["read", "write"] as const)
|
||||
: ([role] as const);
|
||||
for (const resolvedRole of roles) {
|
||||
@@ -594,12 +603,15 @@ const declarationCallableId = (
|
||||
*/
|
||||
export const semanticReadsBeforeWrite = (
|
||||
node: t.Node,
|
||||
parent: t.Node,
|
||||
): boolean =>
|
||||
(t.isAssignmentExpression(parent) &&
|
||||
parent.left === node &&
|
||||
parent.operator !== "=") ||
|
||||
(t.isUpdateExpression(parent) && parent.argument === node);
|
||||
site: t.Node,
|
||||
): boolean => {
|
||||
const target = semanticWriteSiteTarget(site);
|
||||
return (
|
||||
target !== null &&
|
||||
unwrapJavaScriptExpression(target).node === node &&
|
||||
semanticWriteSiteReadsFirst(site)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Role of one identifier occurrence, given the node's ancestry.
|
||||
@@ -620,6 +632,8 @@ export const semanticIdentifierRole = (
|
||||
if (isPatternOwnerDeclaration(patternAncestor)) return null;
|
||||
return "write";
|
||||
}
|
||||
const site = semanticWrappedWriteSite(node, parent, ancestors);
|
||||
if (site !== null && site !== parent && !isDeleteSite(site)) return "write";
|
||||
return semanticReferenceRole(node, parent);
|
||||
};
|
||||
|
||||
@@ -684,6 +698,9 @@ const isPatternNode = (node: t.Node): boolean =>
|
||||
t.isAssignmentPattern(node) ||
|
||||
t.isRestElement(node);
|
||||
|
||||
const isDeleteSite = (site: t.Node): boolean =>
|
||||
t.isUnaryExpression(site, { operator: "delete" });
|
||||
|
||||
const isPatternOwnerDeclaration = (node: t.Node): boolean =>
|
||||
(t.isVariableDeclarator(node) && t.isPattern(node.id)) ||
|
||||
(t.isFunction(node) &&
|
||||
@@ -815,16 +832,15 @@ const commonJsExportName = (
|
||||
if (!t.isMemberExpression(node) && !t.isOptionalMemberExpression(node))
|
||||
return undefined;
|
||||
const key = semanticStaticPropertyKey(node.property, node.computed);
|
||||
// `exports[key]`/`module.exports[key]` assign an export whose name is not
|
||||
// knowable. Report the wildcard rather than the variable name, and never
|
||||
// collapse it into `default`, which would claim a real default export.
|
||||
// A computed nonliteral key is unknown, but the empty string is a real exact
|
||||
// CommonJS export name and must not collapse into the wildcard/default.
|
||||
if (isUnshadowedGlobal(node.object, state, "exports"))
|
||||
return key === null ? "*" : key || "*";
|
||||
return key === null ? "*" : key;
|
||||
if (
|
||||
t.isMemberExpression(node.object) &&
|
||||
isCommonJsModuleExports(node.object, state)
|
||||
)
|
||||
return key === null ? "*" : key || "default";
|
||||
return key === null ? "*" : key;
|
||||
if (isCommonJsModuleExports(node, state)) return "default";
|
||||
return undefined;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user