From 47947eb01cfb5f15fb1d70042e35c02142436d25 Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Sat, 10 Oct 2026 18:38:59 +0800 Subject: [PATCH] perf(javascript): index returned object targets --- .../javascriptSemanticReturnedReferences.ts | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/domain/javascript/javascriptSemanticReturnedReferences.ts b/src/domain/javascript/javascriptSemanticReturnedReferences.ts index 998ec06b40..fbf1dbb56c 100644 --- a/src/domain/javascript/javascriptSemanticReturnedReferences.ts +++ b/src/domain/javascript/javascriptSemanticReturnedReferences.ts @@ -30,6 +30,22 @@ interface Candidate { readonly bindings: ReadonlySet; } +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(); const results = new WeakMap(); + const objectTargets = new WeakMap(); 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, ): 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, +): ObjectTargetIndex => { + const existing = indexes.get(node); + if (existing !== undefined) return existing; + const propertyByName = new Map(); + 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; +};