Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion test/fixtures/dataflow-app/src/hierarchy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/** A minimal first-party class hierarchy — exercises the Neo4j EXTENDS/IMPLEMENTS projection. */
/** A minimal first-party class/interface hierarchy — exercises the Neo4j EXTENDS/IMPLEMENTS
* projection, including interface→interface EXTENDS (`ColoredShape extends Shape`). */

export interface Shape {
area(): number;
Expand All @@ -8,6 +9,10 @@ export interface Labeled {
readonly label: string;
}

export interface ColoredShape extends Shape {
readonly color: string;
}

export class Rectangle implements Shape {
constructor(
protected readonly width: number,
Expand Down
19 changes: 18 additions & 1 deletion test/neo4j-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ describe(":Application node carries analyzer identity (issue #43)", () => {

// ---- Class inheritance: EXTENDS/IMPLEMENTS (issue #33) ------------------------------------------
// dataflow-app's src/hierarchy.ts is a minimal, first-party heritage fixture: `Rectangle implements
// Shape`, `Square extends Rectangle implements Labeled`.
// Shape`, `Square extends Rectangle implements Labeled`, `ColoredShape extends Shape` (interface→
// interface heritage — issue #45).

describe("neo4j inheritance edges (issue #33)", () => {
test("EXTENDS and IMPLEMENTS are declared in the schema catalog", () => {
Expand Down Expand Up @@ -145,4 +146,20 @@ describe("neo4j inheritance edges (issue #33)", () => {
expect(nodeValues.has(e.to.value), `dangling EXTENDS/IMPLEMENTS target ${e.to.value}`).toBe(true);
}
});

test("interface-extends-interface projects an EXTENDS edge with an Interface source AND target (issue #45)", () => {
const coloredShape = nodeBySignature("src/hierarchy.ColoredShape");
const shape = nodeBySignature("src/hierarchy.Shape");
expect(coloredShape, "ColoredShape node").toBeDefined();
expect(shape, "Shape node").toBeDefined();
expect(specificLabel(coloredShape!.labels)).toBe("Interface");
expect(specificLabel(shape!.labels)).toBe("Interface");

const ext = rows.edges.filter((e) => e.type === "EXTENDS");
expect(ext.some((e) => e.from.value === coloredShape!.value && e.to.value === shape!.value)).toBe(true);

const nodeValues = new Set(rows.nodes.map((n) => n.value));
expect(nodeValues.has(coloredShape!.value)).toBe(true);
expect(nodeValues.has(shape!.value)).toBe(true);
});
});
37 changes: 37 additions & 0 deletions test/schema-v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,43 @@ describe("schema v2 — L3 intraprocedural dataflow", () => {
expect(e).toBeGreaterThan(s);
expect(mod.source.slice(s, e)).toBe('let label = "none";');
});

test("@entry/@exit body nodes are source-sliceable and carry the whole-callable span (issue #45)", () => {
// Per extract.ts's emitNode: "ENTRY/EXIT carry the whole callable's span" — unlike statement
// nodes (which slice out just their own text), entry/exit should reproduce the callable's
// entire declaration, including its `export` modifier.
const mod = dfL3.application.symbol_table["src/flow.ts"];
const classify = mod.functions.classify as V2Callable;
const entry = classify.body["@entry"];
const exit = classify.body["@exit"];
expect(entry?.kind).toBe("entry");
expect(exit?.kind).toBe("exit");

const [cs, ce] = (classify.span as { bytes: [number, number] }).bytes;
const callableText = mod.source.slice(cs, ce);
expect(callableText.startsWith("export function classify")).toBe(true);

for (const node of [entry, exit]) {
const [s, e] = (node as { span: { bytes: [number, number] } }).span.bytes;
expect(e).toBeGreaterThan(s);
const slice = mod.source.slice(s, e);
expect(slice.length).toBeGreaterThan(0);
expect(slice).toBe(callableText); // whole-callable span, byte-identical to classify's own span
}
});

// `param` is contracted out before emission (dataflow.ts: `if (n.kind === "param") continue;` in
// emitL3) — a param folds onto `@entry` at L3 and only reappears, synthetically, as
// `@formal_in:N` at L4 (asserted below). So there is no emitted `param`-kind body node to slice;
// slice fidelity for raw `param` is N/A at the emitted-tree level, not merely untested.
test("no raw `param`-kind body node is ever emitted (contracted onto @entry/@formal_in)", () => {
for (const c of allCallables(dfL3.application)) {
for (const node of Object.values(c.body)) expect(node.kind).not.toBe("param");
}
for (const c of allCallables(dfL4.application)) {
for (const node of Object.values(c.body)) expect(node.kind).not.toBe("param");
}
});
});

describe("schema v2 — L4 interprocedural SDG", () => {
Expand Down