Skip to content

DFM extractor: component endLine pinned to declaration line (never back-patched on block end), truncating every component body — verified end-to-end #1350

Description

@inth3shadows

Summary

In the Delphi DFM/FMX extractor, every component node's endLine is pinned to its declaration line and is never back-patched when the matching block end is found. So a component that spans many source lines is recorded with endLine == startLine. Downstream, the context builder slices startLine..endLine to produce the "full body" an agent sees (the codegraph_node sufficiency guarantee), so every DFM component body is truncated to a single line — the agent never sees the component's properties or its On* event bindings. Verified end-to-end against a real indexed project.

Root cause

// src/extraction/dfm-extractor.ts:112-137
const objMatch = line.match(objectPattern);
if (objMatch) {
  const [, , name, typeName] = objMatch;
  const nodeId = generateNodeId(this.filePath, 'component', name!, lineNum);
  this.nodes.push({
    ...
    startLine: lineNum,
    endLine: lineNum,   // <- set at declaration, never updated
    ...
  });
  stack.push(nodeId);
  continue;
}
...
// src/extraction/dfm-extractor.ts:153-156
if (endPattern.test(line)) {
  if (stack.length > 1) stack.pop();   // pops the frame but never revisits its endLine
}

The endPattern branch only pops the id stack; it never sets endLine on the node being closed.

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

Fixture Form1.dfm:

object Form1: TForm1
  Left = 0
  object Button1: TButton
    Left = 10
    Caption = 'Click'
    OnClick = Button1Click
  end
end

After CodeGraph.init + indexAll, the persisted component nodes are:

Form1    lines 1-1     (actual block: 1-8)
Button1  lines 3-3     (actual block: 3-7)

Both are truncated to their declaration line. A consumer building Button1's body from startLine..endLine sees only object Button1: TButton and never the Left, Caption, or the OnClick = Button1Click event binding that lives inside the same node's own block.

Suggested direction

Track, alongside the id stack, the index into this.nodes for each pushed component; on the endPattern match (before popping), set this.nodes[idx].endLine = lineNum for the frame being closed.

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