diff --git a/src/application/JavaScriptSemanticGraphResources.test.ts b/src/application/JavaScriptSemanticGraphResources.test.ts index b35399a56..3f88e0721 100644 --- a/src/application/JavaScriptSemanticGraphResources.test.ts +++ b/src/application/JavaScriptSemanticGraphResources.test.ts @@ -170,6 +170,24 @@ it("projects built-in resource acquisition and release", () => { ).toMatchObject({ status: "partial" }); }); +it("does not project a literal property slot for dynamic destructuring", () => { + const graph = graphFor(` + const source = { token: "TOKEN" }; + const key = getKey(); + const { [key]: value } = source; + `); + + expect( + graph.relations.filter(({ relation }) => relation === "destructures"), + ).toEqual([]); + expect( + graph.nodes.find( + ({ kind, properties }) => + kind === "property-slot" && properties.name === "key", + ), + ).toBeUndefined(); +}); + it("projects literal seeds and static object flow", () => { const graph = graphFor(` const source = { token: "TOKEN", count: 1 }; diff --git a/src/domain/javascriptSemanticAnalysis.dataflow.test.ts b/src/domain/javascriptSemanticAnalysis.dataflow.test.ts index 673672cb1..cd44801f7 100644 --- a/src/domain/javascriptSemanticAnalysis.dataflow.test.ts +++ b/src/domain/javascriptSemanticAnalysis.dataflow.test.ts @@ -36,6 +36,32 @@ describe("JavaScript semantic analysis: dataflow 1", () => { ); }); + it("does not invent property names for dynamic destructuring keys", () => { + const ir = analyzeJavaScriptSemantics(` + const source = { token: "TOKEN" }; + const key = getKey(); + const { [key]: dynamic } = source; + const { ["token"]: literal } = source; + const { token: renamed } = source; + const { token } = source; + const { [1]: numeric } = source; + `); + const destructures = ir.objectOperations.filter( + ({ kind }) => kind === "destructure", + ); + + expect(destructures.map(({ propertyName }) => propertyName)).toEqual([ + "token", + "token", + "token", + "1", + ]); + expect( + destructures.every(({ resolution }) => resolution === "complete"), + ).toBe(true); + expect(topLevelBinding(ir, "dynamic").value.status).toBe("unknown"); + }); + it("does not project positional argument flow after a spread", () => { const ir = analyzeJavaScriptSemantics(` function target(first, second, third) { return third; } diff --git a/src/domain/javascriptSemanticObjects.ts b/src/domain/javascriptSemanticObjects.ts index bc593f6d2..f7f8fa18d 100644 --- a/src/domain/javascriptSemanticObjects.ts +++ b/src/domain/javascriptSemanticObjects.ts @@ -13,7 +13,7 @@ import { type JavaScriptSemanticAnalysisState, } from "./javascriptSemanticState.js"; import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js"; -import { propertyName, range } from "./javascriptStaticAnalysisHelpers.js"; +import { range } from "./javascriptStaticAnalysisHelpers.js"; /** Recover bounded static property reads, writes, spreads, and destructuring. */ export const collectJavaScriptSemanticObjects = ( @@ -103,7 +103,7 @@ const collectDestructuring = ( const objectBindingId = expressionBindingId(node.init, state); for (const property of node.id.properties) { if (!t.isObjectProperty(property)) continue; - const name = propertyName(property.key); + const name = semanticStaticPropertyName(property.key, property.computed); if (name.length === 0) continue; const target = bindingIdentifier(property.value); addObjectOperation(