@kodus/kodus-graph 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodus/kodus-graph",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Code graph builder for Kodus code review — parses source code into structural graphs with nodes, edges, and analysis",
5
5
  "type": "module",
6
6
  "bin": {
@@ -89,13 +89,47 @@ export function buildGraphData(
89
89
  });
90
90
  }
91
91
 
92
- // CALLS edges
92
+ // Build file→functions index to resolve caller from line number
93
+ const functionsByFile = new Map<string, Array<{ qualified_name: string; line_start: number; line_end: number }>>();
94
+ for (const node of nodes) {
95
+ if (node.kind === 'Class' || node.kind === 'Interface' || node.kind === 'Enum') continue;
96
+ const entry = { qualified_name: node.qualified_name, line_start: node.line_start, line_end: node.line_end };
97
+ const list = functionsByFile.get(node.file_path);
98
+ if (list) list.push(entry);
99
+ else functionsByFile.set(node.file_path, [entry]);
100
+ }
101
+ // Sort descending by line_start so inner/nested functions match first
102
+ for (const list of functionsByFile.values()) {
103
+ list.sort((a, b) => b.line_start - a.line_start);
104
+ }
105
+
106
+ // CALLS edges — resolve caller function from call line number
93
107
  for (const ce of callEdges) {
108
+ const sourceFile = ce.source.includes('::') ? ce.source.split('::')[0] : ce.source;
109
+ let sourceQualified: string;
110
+
111
+ if (ce.source.includes('::')) {
112
+ sourceQualified = ce.source;
113
+ } else {
114
+ // Find the innermost function containing this call line
115
+ const fns = functionsByFile.get(ce.source);
116
+ let resolved: string | undefined;
117
+ if (fns) {
118
+ for (const fn of fns) {
119
+ if (ce.line >= fn.line_start && ce.line <= fn.line_end) {
120
+ resolved = fn.qualified_name;
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ sourceQualified = resolved || `${ce.source}::unknown`;
126
+ }
127
+
94
128
  edges.push({
95
129
  kind: 'CALLS',
96
- source_qualified: ce.source.includes('::') ? ce.source : `${ce.source}::unknown`,
130
+ source_qualified: sourceQualified,
97
131
  target_qualified: ce.target,
98
- file_path: ce.source.includes('::') ? ce.source.split('::')[0] : ce.source,
132
+ file_path: sourceFile,
99
133
  line: ce.line,
100
134
  confidence: ce.confidence,
101
135
  });