Skip to content
Merged
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
6 changes: 3 additions & 3 deletions src/build/neo4j/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ function projectField(b: RowBuilder, f: TSField, owner: NodeRef, fileKey: string
// ----------------------------------------------------------------------------------------------

function moduleProps(mod: TSModule, fileKey: string): Props {
// The wire strips the internal fields (module_name, content_hash) — the graph name is the
// file key, exactly as the historical projection of the stripped tree produced.
// `name` is the file key. `content_hash` is what the incremental push diffs against on the next
// run (bolt.ts) -- schema.ts has always declared it on :TSModule, but nothing wrote it (#118).
return prune({
id: mod.id, kind: "module", name: fileKey,
id: mod.id, kind: "module", name: fileKey, content_hash: mod.content_hash ?? null,
is_tsx: mod.is_tsx, is_declaration_file: mod.is_declaration_file,
...span(mod), _module: fileKey,
});
Expand Down
5 changes: 4 additions & 1 deletion src/schema/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ function stripInternal(root: TSApplication): void {
for (const nt of Object.values((t["types"] as Record<string, Record<string, unknown>>) ?? {})) stripType(nt);
};
for (const mod of Object.values(root.symbol_table) as unknown as Record<string, unknown>[]) {
delete mod["content_hash"];
// `content_hash` STAYS on the wire (#118). The Neo4j projection is built from this envelope
// and writes it onto :TSModule, where the incremental push reads it back to find changed
// modules -- stripping it here made that diff compare against NULL forever, so every push was
// a full re-upsert. codeanalyzer-python keeps it for the same reason (schema/py_schema.py).
delete mod["last_modified"];
delete mod["file_size"];
for (const fn of Object.values((mod["functions"] as Record<string, Record<string, unknown>>) ?? {})) stripCallable(fn);
Expand Down
16 changes: 16 additions & 0 deletions test/neo4j-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ describe("neo4j schema conformance", () => {
// The JSON envelope advertises `analyzer{name,version}` (#29); the Neo4j :Application node is the
// co-primary projection of the same envelope and must not diverge on analyzer identity.

// #118: the incremental push diffs each module's stored `content_hash` to find what changed. The
// field was stripped from the wire and never projected, so the diff compared against NULL forever
// and every push was a full re-upsert. schema.ts declared the property, bolt.ts read it, and
// project.ts never wrote it -- three parts of the system disagreeing. The old bolt test seeded the
// value by hand, so it exercised the diff against data the projection could not produce.
describe("module content_hash reaches the graph (#118)", () => {
test("every projected :TSModule carries a non-null content_hash", () => {
const modules = rows.nodes.filter((n) => n.labels.includes("TSModule"));
expect(modules.length).toBeGreaterThan(0);
for (const m of modules) {
expect(m.props["content_hash"], `no content_hash on ${m.value}`).toBeDefined();
expect(typeof m.props["content_hash"]).toBe("string");
}
});
});

describe(":Application node carries analyzer identity (issue #43)", () => {
test("version matches package.json (the same source the JSON envelope's analyzer.version uses)", () => {
const appNode = rows.nodes.find((n) => n.labels.includes("Application"));
Expand Down
Loading