Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
426aa62c10 | ||
|
|
1268cb328f | ||
|
|
f2f56b5804 | ||
|
|
04875e7817 |
@@ -608,6 +608,21 @@ describe("property mutation collection on minified bundles", () => {
|
||||
],
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
it("indexes many returned object methods instead of rescanning the object", () => {
|
||||
const methods = Array.from(
|
||||
{ length: 2000 },
|
||||
(_, index) => `m${index}() { return shared; }`,
|
||||
).join(",");
|
||||
const writes = Array.from(
|
||||
{ length: 2000 },
|
||||
(_, index) => `box.m${index}().p${index} = ${index};`,
|
||||
).join(" ");
|
||||
const value = resultValue(
|
||||
`const shared = { token: "TOKEN" }; const box = { ${methods} }; ${writes} return shared.token;`,
|
||||
);
|
||||
expect(value?.status).toBe("unknown");
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
describe("results of methods on an escaped receiver", () => {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user