fix(pascal): attribute a free routine's calls to it, not the file (#795)

A Pascal/Delphi procedure or function defined ONLY in the implementation section
(no interface declaration, not a class method) had no node of its own, so
extractPascalDefProc's caller lookup fell through to the nodeStack top — the file
node. Every call in such a routine's body was lumped under the unit: callers
returned the file, and impact couldn't attribute the call to the routine. (Methods
were fine — they get a node from their class declaration.)

Fix: when extractPascalDefProc finds no existing node for a FREE routine (a name
with no `.`), create a function node for it and attribute the body's calls to it.
Interface-declared free routines already have a node (found via the methodIndex),
so there's no duplicate; methods keep their existing class-declaration node.

PascalCoin A/B: +511 / -145 — the +511 are calls now correctly attributed to their
actual routine (`allocate_new_datablock -> TDisposables::GetMem`), replacing -145
file-level aggregates; +248 new function nodes for the implementation-only
routines. New synthetic test asserts a free routine's call attributes to it
alongside a method caller. EXTRACTION_VERSION 17->18. Full suite green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-11 09:05:08 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 5342f7a93e
commit dac00e7d44
4 changed files with 50 additions and 4 deletions
+1 -1
View File
@@ -21,4 +21,4 @@
* turns the re-index hint into noise — keep it honest (see CLAUDE.md, "Honesty
* in the product is load-bearing").
*/
export const EXTRACTION_VERSION = 17;
export const EXTRACTION_VERSION = 18;
+23 -3
View File
@@ -4281,10 +4281,30 @@ export class TreeSitterExtractor {
}
}
const parentId =
let parentId =
this.methodIndex.get(fullNameKey) ||
this.methodIndex.get(shortNameKey) ||
this.nodeStack[this.nodeStack.length - 1];
this.methodIndex.get(shortNameKey);
// No existing node? This is an implementation-only **free** procedure/function
// (`procedure Helper; begin … end;` with no interface declaration and not a
// class method). Create a function node so its body's calls attribute to it,
// not to the enclosing file/module. A method (`TClass.Method`, a dotted name)
// always has a node from its class declaration, so this only fires for free
// routines — and the methodIndex lookup above already covers interface-declared
// free routines, so there's no duplicate.
if (!parentId && !fullName.includes('.')) {
const fnNode = this.createNode('function', fullName, declProc, {
signature: this.extractor?.getSignature?.(declProc, this.source),
visibility: this.extractor?.getVisibility?.(declProc),
});
if (fnNode) {
parentId = fnNode.id;
this.methodIndex.set(fullNameKey, fnNode.id);
if (!this.methodIndex.has(shortNameKey)) this.methodIndex.set(shortNameKey, fnNode.id);
}
}
if (!parentId) parentId = this.nodeStack[this.nodeStack.length - 1];
if (!parentId) return;
// Visit the block for calls