mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
refactor(javascript): group domain semantics and contracts (#907)
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import type {
|
||||
JavaScriptSemanticCallable,
|
||||
JavaScriptSemanticObjectOperation,
|
||||
} from "./javascriptSemanticIr.js";
|
||||
import {
|
||||
semanticCallableIdForNode,
|
||||
semanticReadsBeforeWrite,
|
||||
semanticStaticPropertyKey,
|
||||
} from "./javascriptSemanticProjection.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
|
||||
/** Recover bounded static property reads, writes, spreads, and destructuring. */
|
||||
export const collectJavaScriptSemanticObjects = (
|
||||
program: t.Program,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
callables: readonly JavaScriptSemanticCallable[],
|
||||
): JavaScriptSemanticObjectOperation[] => {
|
||||
const output: JavaScriptSemanticObjectOperation[] = [];
|
||||
const callableIds = new Set(callables.map(({ callableId }) => callableId));
|
||||
const callableStack: string[] = [];
|
||||
const context: ObjectCollectionContext = { state, output };
|
||||
traverseJavaScriptAst(program, {
|
||||
enter: (node, parent) => {
|
||||
const callableId = semanticCallableIdForNode(node);
|
||||
if (callableId !== null && callableIds.has(callableId))
|
||||
callableStack.push(callableId);
|
||||
const ownerCallableId = callableStack.at(-1) ?? null;
|
||||
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node))
|
||||
collectMember(node, parent, ownerCallableId, context);
|
||||
else if (
|
||||
t.isSpreadElement(node) &&
|
||||
parent !== null &&
|
||||
t.isObjectExpression(parent)
|
||||
)
|
||||
addObjectOperation(
|
||||
{
|
||||
node,
|
||||
kind: "spread",
|
||||
ownerCallableId,
|
||||
objectBindingId: expressionBindingId(node.argument, state),
|
||||
targetBindingId: null,
|
||||
propertyName: null,
|
||||
},
|
||||
state,
|
||||
output,
|
||||
);
|
||||
else if (t.isVariableDeclarator(node) && t.isObjectPattern(node.id))
|
||||
collectDestructuring(node, ownerCallableId, state, output);
|
||||
},
|
||||
exit: (node) => {
|
||||
const callableId = semanticCallableIdForNode(node);
|
||||
if (callableId !== null && callableStack.at(-1) === callableId)
|
||||
callableStack.pop();
|
||||
},
|
||||
});
|
||||
return output;
|
||||
};
|
||||
|
||||
interface ObjectCollectionContext {
|
||||
readonly state: JavaScriptSemanticAnalysisState;
|
||||
readonly output: JavaScriptSemanticObjectOperation[];
|
||||
}
|
||||
|
||||
const collectMember = (
|
||||
node: t.MemberExpression | t.OptionalMemberExpression,
|
||||
parent: t.Node | null,
|
||||
ownerCallableId: string | null,
|
||||
context: ObjectCollectionContext,
|
||||
): void => {
|
||||
const { state, output } = context;
|
||||
const name = semanticStaticPropertyKey(node.property, node.computed);
|
||||
if (name === null) return;
|
||||
const write =
|
||||
(t.isAssignmentExpression(parent) && parent.left === node) ||
|
||||
(t.isUpdateExpression(parent) && parent.argument === node);
|
||||
const readsBeforeWrite = write && semanticReadsBeforeWrite(node, parent);
|
||||
if (readsBeforeWrite)
|
||||
addObjectOperation(
|
||||
{
|
||||
node,
|
||||
kind: "read",
|
||||
ownerCallableId,
|
||||
objectBindingId: expressionBindingId(node.object, state),
|
||||
targetBindingId: null,
|
||||
propertyName: name,
|
||||
},
|
||||
state,
|
||||
output,
|
||||
);
|
||||
addObjectOperation(
|
||||
{
|
||||
node,
|
||||
kind: write ? "write" : "read",
|
||||
ownerCallableId,
|
||||
objectBindingId: expressionBindingId(node.object, state),
|
||||
targetBindingId: null,
|
||||
propertyName: name,
|
||||
},
|
||||
state,
|
||||
output,
|
||||
);
|
||||
};
|
||||
|
||||
const collectDestructuring = (
|
||||
node: t.VariableDeclarator,
|
||||
ownerCallableId: string | null,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
output: JavaScriptSemanticObjectOperation[],
|
||||
): void => {
|
||||
if (!t.isObjectPattern(node.id)) return;
|
||||
const objectBindingId = expressionBindingId(node.init, state);
|
||||
for (const property of node.id.properties) {
|
||||
if (!t.isObjectProperty(property)) continue;
|
||||
const name = semanticStaticPropertyKey(property.key, property.computed);
|
||||
if (name === null) continue;
|
||||
const target = bindingIdentifier(property.value);
|
||||
addObjectOperation(
|
||||
{
|
||||
node: property,
|
||||
kind: "destructure",
|
||||
ownerCallableId,
|
||||
objectBindingId,
|
||||
targetBindingId:
|
||||
target === null
|
||||
? null
|
||||
: (resolveSemanticBindingState(state, target, target.name)
|
||||
?.bindingId ?? null),
|
||||
propertyName: name,
|
||||
},
|
||||
state,
|
||||
output,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
interface AddObjectOperationInput {
|
||||
readonly node: t.Node;
|
||||
readonly kind: JavaScriptSemanticObjectOperation["kind"];
|
||||
readonly ownerCallableId: string | null;
|
||||
readonly objectBindingId: string | null;
|
||||
readonly targetBindingId: string | null;
|
||||
readonly propertyName: string | null;
|
||||
}
|
||||
|
||||
const addObjectOperation = (
|
||||
input: AddObjectOperationInput,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
output: JavaScriptSemanticObjectOperation[],
|
||||
): void => {
|
||||
output.push({
|
||||
objectOperationId: `object:${input.kind}:${String(input.node.start ?? -1)}:${String(input.node.end ?? -1)}`,
|
||||
kind: input.kind,
|
||||
location: range(input.node),
|
||||
ownerCallableId: input.ownerCallableId,
|
||||
objectBindingId: input.objectBindingId,
|
||||
targetBindingId: input.targetBindingId,
|
||||
propertyName: input.propertyName,
|
||||
resolution:
|
||||
input.kind === "spread"
|
||||
? input.objectBindingId === null
|
||||
? "partial"
|
||||
: "complete"
|
||||
: input.objectBindingId === null ||
|
||||
input.propertyName === null ||
|
||||
(input.kind === "destructure" && input.targetBindingId === null)
|
||||
? "partial"
|
||||
: "complete",
|
||||
});
|
||||
};
|
||||
|
||||
const expressionBindingId = (
|
||||
node: t.Node | null | undefined,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): string | null =>
|
||||
t.isIdentifier(node)
|
||||
? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null)
|
||||
: null;
|
||||
|
||||
const bindingIdentifier = (node: t.Node): t.Identifier | null => {
|
||||
if (t.isIdentifier(node)) return node;
|
||||
if (t.isAssignmentPattern(node) && t.isIdentifier(node.left))
|
||||
return node.left;
|
||||
return null;
|
||||
};
|
||||
Reference in New Issue
Block a user