import { describe, expect, it } from "vitest"; import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; import { onlyCallable, topLevelBinding, } from "./javascriptSemanticAnalysis.fixture.js"; describe("JavaScript semantic analysis: dataflow 1", () => { it("recovers static object reads, writes, spreads, and destructuring", () => { const ir = analyzeJavaScriptSemantics(` const source = { token: "TOKEN", count: 1 }; const { token } = source; const copy = { ...source }; source.count = 2; const read = source.token; `); expect( ir.objectOperations.map(({ kind, propertyName, resolution }) => ({ kind, propertyName, resolution, })), ).toEqual( expect.arrayContaining([ { kind: "destructure", propertyName: "token", resolution: "complete", }, { kind: "spread", propertyName: null, resolution: "complete" }, { kind: "write", propertyName: "count", resolution: "complete" }, { kind: "read", propertyName: "token", resolution: "complete" }, ]), ); }); it("does not project positional argument flow after a spread", () => { const ir = analyzeJavaScriptSemantics(` function target(first, second, third) { return third; } target(one, ...rest, three); `); expect(ir.argumentFlows.map(({ argumentIndex }) => argumentIndex)).toEqual([ 0, ]); }); it("retains all statically recovered call, flow, capture, and frontier facts", () => { const ir = analyzeJavaScriptSemantics( ` const captured = 1; function target(value, other) { return value + other + captured; } target(1, 2); target(3, 4); object[first]; object[second]; `, ); expect(ir.callSites).toHaveLength(2); expect(ir.callSites.map(({ arguments: args }) => args)).toEqual([ [ expect.objectContaining({ index: 0, spread: false }), expect.objectContaining({ index: 1, spread: false }), ], [ expect.objectContaining({ index: 0, spread: false }), expect.objectContaining({ index: 1, spread: false }), ], ]); expect(ir.argumentFlows).toHaveLength(4); expect(ir.callReturnFlows).toHaveLength(2); expect(ir.closureCaptures).toHaveLength(1); expect(ir.frontiers).toHaveLength(2); expect(ir.coverage.status).toBe("complete"); }); it("recovers only direct return sites with literal and unknown object fields", () => { const ir = analyzeJavaScriptSemantics(` export default function parseMarkdown(value) { if (value.startsWith("# ")) return { type: "heading", depth: 1, text: value.slice(2) }; function nested() { return { type: "nested" }; } const arrow = () => ({ type: "arrow" }); void nested; void arrow; return { type: "paragraph", text: value.replaceAll("x", "y") }; } `); const parse = onlyCallable(ir, "parseMarkdown"); expect(parse.returnCoverage).toEqual({ status: "complete", retainedCount: 2, omittedCount: 0, }); expect(parse.returnSites).toHaveLength(2); expect(parse.returnSites[0]?.value).toMatchObject({ status: "object", unknownProperties: false, omittedProperties: 0, properties: expect.arrayContaining([ { name: "depth", value: { status: "literal", value: 1 } }, { name: "type", value: { status: "literal", value: "heading" } }, { name: "text", value: { status: "unknown", reason: "Unsupported CallExpression value.", }, }, ]), }); expect(onlyCallable(ir, "nested").returnSites).toHaveLength(1); expect(onlyCallable(ir, "arrow").returnSites).toHaveLength(1); expect(ir.moduleLinks).toContainEqual( expect.objectContaining({ exportedName: "default", callableId: parse.callableId, }), ); }); }); describe("JavaScript semantic analysis: dataflow 2", () => { it("retains partial property coverage and empty returns", () => { const partial = analyzeJavaScriptSemantics(` const spread = () => ({ type: "spread", ...dynamic }); const computed = () => ({ type: "computed", [key]: 1 }); function noReturn() { throw new Error("stop"); } function emptyReturn() { return; } `); expect(onlyCallable(partial, "spread").returnSites[0]?.value).toMatchObject( { status: "object", unknownProperties: true, omittedProperties: null, }, ); expect( onlyCallable(partial, "computed").returnSites[0]?.value, ).toMatchObject({ status: "object", unknownProperties: true, omittedProperties: 1, }); expect(onlyCallable(partial, "noReturn").returnSites).toEqual([]); expect(onlyCallable(partial, "emptyReturn").returnSites[0]?.value).toEqual({ status: "unknown", reason: "Return has no value.", }); }); it("fails closed on assignment ambiguity and alias cycles", () => { const ambiguous = analyzeJavaScriptSemantics(` let current = "first"; current = "second"; const left = right; const right = left; `); expect(topLevelBinding(ambiguous, "current").value).toMatchObject({ status: "ambiguous", }); expect(topLevelBinding(ambiguous, "left").value).toMatchObject({ status: "cycle", }); expect(topLevelBinding(ambiguous, "right").provenance).toMatchObject({ status: "cycle", }); }); it("does not invent exact module paths for dynamic property access", () => { const ir = analyzeJavaScriptSemantics(` const key = getKey(); const dynamicMember = require("electron")[key]; const { [key]: dynamicBinding } = require("electron"); `); expect(topLevelBinding(ir, "dynamicMember").provenance).toMatchObject({ status: "unknown", origins: [], }); expect(topLevelBinding(ir, "dynamicBinding").provenance).toMatchObject({ status: "unknown", origins: [], }); expect( ir.moduleLinks.filter(({ localName }) => ["dynamicMember", "dynamicBinding"].includes(localName ?? ""), ), ).toEqual([]); }); it("retains all scopes, bindings, and references without truncation", () => { const ir = analyzeJavaScriptSemantics( ` const retained = "yes"; const omitted = "no"; retained; function nested(value) { return retained + value; } `, ); expect(ir.bindings.map(({ name }) => name)).toEqual( expect.arrayContaining(["retained", "omitted", "value"]), ); expect(ir.callables.map(({ name }) => name)).toContain("nested"); expect(ir.coverage.status).toBe("complete"); expect(ir.references.filter(({ name }) => name === "retained")).toEqual([ expect.objectContaining({ resolution: "resolved", bindingId: topLevelBinding(ir, "retained").bindingId, }), expect.objectContaining({ resolution: "resolved", bindingId: topLevelBinding(ir, "retained").bindingId, }), ]); }); });