Skip to content

generateNodeId omits column/offset: same-line same-name same-kind symbols (e.g. one-line getter/setter) collide and one node is silently dropped by INSERT OR REPLACE — verified end-to-end #1349

Description

@inth3shadows

Summary

generateNodeId derives a node's identity from filePath:kind:name:line without any column/offset. Two symbols that share a file, kind, name, and line therefore collapse to the same id, and since nodes are persisted with INSERT OR REPLACE INTO nodes on a PRIMARY KEY (id), the second silently overwrites the first — the earlier node's row (and anything an edge pointed at via that id) is lost. The canonical trigger is a one-line getter/setter pair (get x() {...} set x() {...}), a common real-world pattern. Verified end-to-end against a real indexed project (built dist, real SQLite DB).

Root cause

// src/extraction/tree-sitter-helpers.ts:18-30
export function generateNodeId(
  filePath: string,
  kind: NodeKind,
  name: string,
  line: number
): string {
  const hash = crypto
    .createHash('sha256')
    .update(`${filePath}:${kind}:${name}:${line}`) // <- no column / startIndex
    .digest('hex')
    .substring(0, 32);
  return `${kind}:${hash}`;
}

A getter and setter for the same property are both kind: 'method', both name: 'x', and when written on one line both line: N → identical id. On write (INSERT OR REPLACE, keyed on id TEXT PRIMARY KEY) the setter clobbers the getter's row.

Repro (executed end-to-end against a real indexed project)

Fixture point.ts:

export class Point {
  get x() { return this._x; } set x(v: number) { this._x = v; }
  _x = 0;
}

After CodeGraph.init + indexAll, querying persisted nodes named x:

rows persisted for name='x': 1   (source declares 2: getter at col 2, setter at col 30)
  method  L2  col30  method:7450f331e3093417b66c24dfd082db8b

Only the setter survives; the getter node is gone. Extracting the same source in isolation confirms two distinct method nodes are produced (getter startColumn=2, setter startColumn=30) with the same computed id, so the collision happens at id-generation time and the loss happens at DB write time.

Scope / honest severity note

This is structural — generateNodeId is the shared id function for all languages, so any two same-kind same-name symbols on one line collide (one-line getter/setter pairs are the most common instance; minified/transpiled single-line output is another). Multi-line getter/setter pairs — the more common formatting — do not collide, since the line differs, so the everyday blast radius is bounded. Flagging it because the failure is silent (no error, no diagnostic) and the fix is small.

Suggested direction

Fold node.startPosition.column (or startIndex) into the hash input, e.g. `${filePath}:${kind}:${name}:${line}:${column}`. Ids are recomputed per-extraction and edges are built in the same pass, so adding column stays internally consistent within a re-index.

Environment

main @ 2d72891, package 1.4.1, Node 22.23.1 (Linux). Reproduces on a clean npm ci && npm run build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions