|
|
|
@@ -30,6 +30,22 @@ interface Candidate {
|
|
|
|
|
readonly bindings: ReadonlySet<string>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ObjectTargetIndex {
|
|
|
|
|
readonly propertyByName: ReadonlyMap<
|
|
|
|
|
string,
|
|
|
|
|
t.ObjectMethod | t.ObjectProperty
|
|
|
|
|
>;
|
|
|
|
|
readonly trailingUnknownsByProperty: ReadonlyMap<
|
|
|
|
|
t.ObjectMethod | t.ObjectProperty,
|
|
|
|
|
ObjectTargetUnknown
|
|
|
|
|
>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ObjectTargetUnknown {
|
|
|
|
|
readonly node: t.ObjectMethod | t.ObjectProperty | t.SpreadElement;
|
|
|
|
|
readonly previous: ObjectTargetUnknown | undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Resolve local result references conservatively, without claiming call execution. */
|
|
|
|
|
export const createSemanticReturnedReferences = (
|
|
|
|
|
state: JavaScriptSemanticAnalysisState,
|
|
|
|
@@ -39,6 +55,7 @@ export const createSemanticReturnedReferences = (
|
|
|
|
|
);
|
|
|
|
|
const bindingCache = new Map<string, LocalCallableResolution>();
|
|
|
|
|
const results = new WeakMap<t.Node, readonly SemanticReturnedReference[]>();
|
|
|
|
|
const objectTargets = new WeakMap<t.ObjectExpression, ObjectTargetIndex>();
|
|
|
|
|
return (callee) => {
|
|
|
|
|
const existing = results.get(callee);
|
|
|
|
|
if (existing !== undefined) return existing;
|
|
|
|
@@ -115,7 +132,7 @@ export const createSemanticReturnedReferences = (
|
|
|
|
|
} else if (t.isFunction(node) && item.path.length === 0)
|
|
|
|
|
functions.add(node);
|
|
|
|
|
else if (t.isObjectExpression(node))
|
|
|
|
|
selectObjectTargets(node, item, pending);
|
|
|
|
|
selectObjectTargets(node, item, pending, objectTargets);
|
|
|
|
|
else if (t.isArrayExpression(node)) {
|
|
|
|
|
const [key, ...path] = item.path;
|
|
|
|
|
const index =
|
|
|
|
@@ -187,8 +204,38 @@ const selectObjectTargets = (
|
|
|
|
|
node: t.ObjectExpression,
|
|
|
|
|
item: Candidate,
|
|
|
|
|
pending: Candidate[],
|
|
|
|
|
indexes: WeakMap<t.ObjectExpression, ObjectTargetIndex>,
|
|
|
|
|
): void => {
|
|
|
|
|
const [key, ...path] = item.path;
|
|
|
|
|
if (key !== null && key !== undefined) {
|
|
|
|
|
const index = objectTargetIndex(node, indexes);
|
|
|
|
|
const property = index.propertyByName.get(String(key));
|
|
|
|
|
if (property !== undefined) {
|
|
|
|
|
const unknowns: ObjectTargetUnknown[] = [];
|
|
|
|
|
for (
|
|
|
|
|
let unknown = index.trailingUnknownsByProperty.get(property);
|
|
|
|
|
unknown !== undefined;
|
|
|
|
|
unknown = unknown.previous
|
|
|
|
|
)
|
|
|
|
|
unknowns.push(unknown);
|
|
|
|
|
for (const { node: unknown } of unknowns.reverse())
|
|
|
|
|
pending.push({
|
|
|
|
|
...item,
|
|
|
|
|
node: t.isSpreadElement(unknown)
|
|
|
|
|
? unknown.argument
|
|
|
|
|
: t.isObjectProperty(unknown)
|
|
|
|
|
? unknown.value
|
|
|
|
|
: unknown,
|
|
|
|
|
...(t.isSpreadElement(unknown) ? {} : { path }),
|
|
|
|
|
});
|
|
|
|
|
pending.push({
|
|
|
|
|
...item,
|
|
|
|
|
node: t.isObjectProperty(property) ? property.value : property,
|
|
|
|
|
path,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let replaced = false;
|
|
|
|
|
for (const property of [...node.properties].reverse()) {
|
|
|
|
|
if (replaced) break;
|
|
|
|
@@ -211,3 +258,35 @@ const selectObjectTargets = (
|
|
|
|
|
replaced = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const objectTargetIndex = (
|
|
|
|
|
node: t.ObjectExpression,
|
|
|
|
|
indexes: WeakMap<t.ObjectExpression, ObjectTargetIndex>,
|
|
|
|
|
): ObjectTargetIndex => {
|
|
|
|
|
const existing = indexes.get(node);
|
|
|
|
|
if (existing !== undefined) return existing;
|
|
|
|
|
const propertyByName = new Map<string, t.ObjectMethod | t.ObjectProperty>();
|
|
|
|
|
const trailingUnknownsByProperty = new Map<
|
|
|
|
|
t.ObjectMethod | t.ObjectProperty,
|
|
|
|
|
ObjectTargetUnknown
|
|
|
|
|
>();
|
|
|
|
|
let trailingUnknown: ObjectTargetUnknown | undefined;
|
|
|
|
|
for (const property of [...node.properties].reverse()) {
|
|
|
|
|
if (t.isSpreadElement(property)) {
|
|
|
|
|
trailingUnknown = { node: property, previous: trailingUnknown };
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const name = semanticStaticPropertyKey(property.key, property.computed);
|
|
|
|
|
if (name === null) {
|
|
|
|
|
trailingUnknown = { node: property, previous: trailingUnknown };
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (propertyByName.has(name)) continue;
|
|
|
|
|
propertyByName.set(name, property);
|
|
|
|
|
if (trailingUnknown !== undefined)
|
|
|
|
|
trailingUnknownsByProperty.set(property, trailingUnknown);
|
|
|
|
|
}
|
|
|
|
|
const created = { propertyByName, trailingUnknownsByProperty };
|
|
|
|
|
indexes.set(node, created);
|
|
|
|
|
return created;
|
|
|
|
|
};
|
|
|
|
|