diff --git a/src/domain/webBundleAnalyzer.test.ts b/src/domain/webBundleAnalyzer.test.ts index ad10ed772d..16399bd8cc 100644 --- a/src/domain/webBundleAnalyzer.test.ts +++ b/src/domain/webBundleAnalyzer.test.ts @@ -369,3 +369,45 @@ describe("web metadata key semantics", () => { ]); }); }); + +it("does not infer call names from dynamic computed identifiers", () => { + const result = analyzeCapturedWebBundle( + inspection(` + const fetch = "render"; + const require = "render"; + const registerTool = "render"; + window[fetch]("/render-only"); + window[require]("./not-a-module.js"); + document.modelContext[registerTool]({ name: "not-a-tool" }); + `), + ); + + expect(result.observations.endpoints).toEqual([]); + expect(result.observations.chunks.edges).toEqual([]); + expect(result.observations.webmcp_declarations).toEqual([]); +}); + +it("retains direct and literal computed call names", () => { + const result = analyzeCapturedWebBundle( + inspection(` + window.fetch("/direct"); + window["fetch"]("/literal"); + window[receiver].fetch("/nested-receiver"); + window.require("./direct.js"); + window["require"]("./literal.js"); + document.modelContext["registerTool"]({ name: "literal-tool" }); + `), + ); + + expect(result.observations.endpoints.map(({ value }) => value)).toEqual([ + "/direct", + "/literal", + "/nested-receiver", + ]); + expect( + result.observations.chunks.edges.map(({ specifier }) => specifier), + ).toEqual(["./direct.js", "./literal.js"]); + expect(result.observations.webmcp_declarations).toEqual([ + expect.objectContaining({ name: "literal-tool" }), + ]); +}); diff --git a/src/domain/webBundleAnalyzerAst.ts b/src/domain/webBundleAnalyzerAst.ts index ee342161e8..a8f2ae25ba 100644 --- a/src/domain/webBundleAnalyzerAst.ts +++ b/src/domain/webBundleAnalyzerAst.ts @@ -11,7 +11,11 @@ export const calleeName = (callee: t.Node): string => { const object = t.isExpression(callee.object) ? calleeName(callee.object) : ""; - const property = propertyName(callee.property); + const property = semanticStaticPropertyName( + callee.property, + callee.computed, + ); + if (property === "") return ""; return object === "" ? property : `${object}.${property}`; } return ""; @@ -21,12 +25,6 @@ export const stringArgument = ( value: t.Node | null | undefined, ): string | undefined => (t.isStringLiteral(value) ? value.value : undefined); -export const propertyName = (value: t.Node): string => { - if (t.isIdentifier(value)) return value.name; - if (t.isStringLiteral(value)) return value.value; - return ""; -}; - export const objectValue = ( object: t.ObjectExpression, name: string,