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, propertyPath, resolution }) => ({ kind, propertyPath, resolution, })), ).toEqual( expect.arrayContaining([ { kind: "destructure", propertyPath: ["token"], resolution: "complete", }, { kind: "spread", propertyPath: [], resolution: "complete" }, { kind: "write", propertyPath: ["count"], resolution: "complete" }, { kind: "read", propertyPath: ["token"], resolution: "complete" }, ]), ); }); 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(({ propertyPath }) => propertyPath)).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; } 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", presence: "present", value: { status: "literal", value: 1 }, }, { name: "type", presence: "present", value: { status: "literal", value: "heading" }, }, { name: "text", presence: "present", 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 call result flows", () => { it("links only directly assigned scalar call results", () => { const ir = analyzeJavaScriptSemantics(` function run() { const direct = produce(); const { field } = unpack(); const nested = outer(inner()); return direct; } `); const bindingNames = new Map( ir.bindings.map(({ bindingId, name }) => [bindingId, name]), ); expect( ir.callResultFlows.map(({ bindingId }) => bindingNames.get(bindingId)), ).toEqual(["direct", "nested"]); expect(ir.callSites).toHaveLength(4); expect(ir.callResultFlows.map(({ callSiteId }) => callSiteId)).toEqual([ ir.callSites[0]?.callSiteId, ir.callSites[2]?.callSiteId, ]); }); }); describe("JavaScript semantic analysis: read-modify-write", () => { it.each(["+= 1", "++", "--", "||= 1", "&&= 1", "??= 1"])( "retains the property read in a read-modify-write operation: %s", (operator) => { const ir = analyzeJavaScriptSemantics(` const source = { count: 1 }; source.count ${operator}; `); expect(ir.objectOperations.map(({ kind }) => kind)).toEqual([ "read", "write", ]); }, ); it("keeps plain member assignment write-only and member access read-only", () => { const ir = analyzeJavaScriptSemantics(` const source = { count: 1 }; source.count = 2; const value = source.count; `); expect(ir.objectOperations.map(({ kind }) => kind)).toEqual([ "write", "read", ]); }); it("does not model a compound binding write as a competing replacement", () => { const ir = analyzeJavaScriptSemantics(` let count = 1; count += 2; `); expect(topLevelBinding(ir, "count").value).toMatchObject({ status: "unknown", }); }); }); 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, }), ]); }); }); describe("primitive addition values", () => { it.each([ ["true + 1", 2], ["null + 2", 2], ["false + true", 1], ['"x" + false', "xfalse"], ['null + "x"', "nullx"], ["3 + 4", 7], ['1 + 2 + "3"', "33"], ['1 + (2 + "3")', "123"], ['(1 + 2) + ("3" + 4)', "334"], ])("recovers %s using primitive coercion", (expression, expected) => { const ir = analyzeJavaScriptSemantics(`const answer = ${expression};`); expect(topLevelBinding(ir, "answer").value).toEqual({ status: "literal", value: expected, }); }); }); describe("array positional value recovery", () => { it("keeps holes at their actual destructuring indices", () => { const ir = analyzeJavaScriptSemantics(` const list = ["zero", , "two"]; const [first, second, third] = list; `); expect(topLevelBinding(ir, "first").value).toEqual({ status: "literal", value: "zero", }); expect(topLevelBinding(ir, "second").value.status).toBe("unknown"); expect(topLevelBinding(ir, "third").value).toEqual({ status: "literal", value: "two", }); }); it("projects canonical array indices without treating arbitrary strings as indices", () => { const ir = analyzeJavaScriptSemantics(` const list = ["zero", "one"]; const numeric = list[1]; const quoted = list["1"]; const leadingZero = list["01"]; const negative = list[-1]; `); for (const name of ["numeric", "quoted"]) expect(topLevelBinding(ir, name).value).toEqual({ status: "literal", value: "one", }); for (const name of ["leadingZero", "negative"]) expect(topLevelBinding(ir, name).value.status).toBe("unknown"); }); it("does not shift trailing values across an unknown spread", () => { const ir = analyzeJavaScriptSemantics(` const values = ["first", ...unknownValues, "last"]; const [first, second] = values; `); expect(topLevelBinding(ir, "first").value).toEqual({ status: "literal", value: "first", }); expect(topLevelBinding(ir, "second").value.status).toBe("unknown"); }); }); describe("object value overwrite boundaries", () => { it("uses the last named property and keeps later exact overwrites", () => { const ir = analyzeJavaScriptSemantics(` const duplicate = { key: "old", key: "new" }; const selected = duplicate.key; const later = { key: "old", ...unknownObject, key: "final" }; const final = later.key; `); expect(topLevelBinding(ir, "selected").value).toEqual({ status: "literal", value: "new", }); expect(topLevelBinding(ir, "final").value).toEqual({ status: "literal", value: "final", }); }); it("does not retain an exact earlier value after an unknown overwrite", () => { const ir = analyzeJavaScriptSemantics(` const spread = { key: "old", ...unknownObject }; const dynamic = { key: "old", [unknownKey]: "new" }; const method = { key: "old", key() { return "new"; } }; const spreadValue = spread.key; const dynamicValue = dynamic.key; const methodValue = method.key; `); for (const name of ["spreadValue", "dynamicValue", "methodValue"]) expect(topLevelBinding(ir, name).value.status).toBe("unknown"); }); it("admits literal computed keys while excluding prototype setters", () => { const ir = analyzeJavaScriptSemantics(` const object = { ["key"]: "value", __proto__: { prototype: true } }; const selected = object.key; const prototype = object.__proto__; `); expect(topLevelBinding(ir, "selected").value).toEqual({ status: "literal", value: "value", }); expect(topLevelBinding(ir, "prototype").value.status).toBe("unknown"); }); }); it("retains shorthand __proto__ as an own data property", () => { const ir = analyzeJavaScriptSemantics(` const __proto__ = "own"; const object = { __proto__ }; const selected = object.__proto__; `); expect(topLevelBinding(ir, "selected").value).toEqual({ status: "literal", value: "own", }); }); describe("nonfinite static values", () => { it.each(['+"not-a-number"', '-"not-a-number"', "1e308 + 1e308", "1e309"])( "keeps %s unknown instead of publishing a non-JSON number", (expression) => { const ir = analyzeJavaScriptSemantics(`const answer = ${expression};`); expect(topLevelBinding(ir, "answer").value.status).toBe("unknown"); }, ); it("does not collapse an uncertain nonfinite branch to its finite alternative", () => { const ir = analyzeJavaScriptSemantics( 'const answer = condition ? 1 : +"invalid";', ); expect(topLevelBinding(ir, "answer").value.status).toBe("ambiguous"); }); }); describe("binding write invalidation", () => { it.each([ ["let value = 1; value++; const observed = value;", "unknown"], ["let value = 1; --value; const observed = value;", "unknown"], [ 'let value = "old"; [value] = ["new"]; const observed = value;', "ambiguous", ], [ 'let value = "old"; ({key: value} = {key: "new"}); const observed = value;', "ambiguous", ], [ 'let value = "old"; for (value of ["new"]) {} const observed = value;', "ambiguous", ], [ 'let value = "old"; for (value in {new: true}) {} const observed = value;', "ambiguous", ], ] as const)( "does not retain an exact initializer across %s", (source, status) => { const ir = analyzeJavaScriptSemantics(source); expect(topLevelBinding(ir, "observed").value.status).toBe(status); expect( topLevelBinding(ir, "value").definitions.some( ({ kind }) => kind === "assignment", ), ).toBe(true); }, ); it("does not treat destructuring keys or member owners as assigned bindings", () => { const ir = analyzeJavaScriptSemantics(` const key = "stable"; const owner = "stable"; let target = "old"; ({key: target} = {key: "new"}); ({owner: object.slot} = {owner: "new"}); const preservedKey = key; const preservedOwner = owner; `); for (const name of ["preservedKey", "preservedOwner"]) expect(topLevelBinding(ir, name).value).toEqual({ status: "literal", value: "stable", }); }); }); describe("JavaScript semantic analysis: unknown default inputs", () => { it.each([ 'function launch(mode = "safe") { return mode; }', 'function launch({ mode } = { mode: "safe" }) { return mode; }', 'function launch([mode] = ["safe"]) { return mode; }', ])("does not assume an optional argument is omitted: %s", (source) => { const ir = analyzeJavaScriptSemantics(source); expect(onlyCallable(ir, "launch").returnSites[0]?.value.status).toBe( "unknown", ); }); it("preserves known declaration inputs and unknown catch bindings", () => { const ir = analyzeJavaScriptSemantics(` const { mode: retained = "fallback" } = { mode: "actual" }; function caught() { try { risky(); } catch ({ message = "fallback" }) { return message; } } `); expect(topLevelBinding(ir, "retained").value).toMatchObject({ status: "literal", value: "actual", }); expect(onlyCallable(ir, "caught").returnSites[0]?.value.status).toBe( "unknown", ); }); });