* Create edges from resolved references
(resolved: ResolvedRef[])
| 1050 | * Create edges from resolved references |
| 1051 | */ |
| 1052 | createEdges(resolved: ResolvedRef[]): Edge[] { |
| 1053 | return resolved.map((ref) => { |
| 1054 | // `function_ref` (#756) is internal-only: it persists as a `references` |
| 1055 | // edge (the registration site depends on the callback), distinguishable |
| 1056 | // by metadata.resolvedBy === 'function-ref'. callers/impact already |
| 1057 | // traverse `references`, so registration sites surface with no |
| 1058 | // graph-layer changes. |
| 1059 | let kind: Edge['kind'] = |
| 1060 | ref.original.referenceKind === 'function_ref' ? 'references' : ref.original.referenceKind; |
| 1061 | |
| 1062 | // Promote "extends" to "implements" when a class/struct targets an interface |
| 1063 | if (kind === 'extends') { |
| 1064 | const targetNode = this.queries.getNodeById(ref.targetNodeId); |
| 1065 | if (targetNode && (targetNode.kind === 'interface' || targetNode.kind === 'protocol')) { |
| 1066 | const sourceNode = this.queries.getNodeById(ref.original.fromNodeId); |
| 1067 | if (sourceNode && sourceNode.kind !== 'interface' && sourceNode.kind !== 'protocol') { |
| 1068 | kind = 'implements'; |
| 1069 | } |
| 1070 | } |
| 1071 | } |
| 1072 | |
| 1073 | // Promote "calls" to "instantiates" when the resolved target is a |
| 1074 | // class/struct. Languages without a `new` keyword (Python, Ruby) |
| 1075 | // express instantiation as `Foo()` — extraction can't tell that |
| 1076 | // apart from a function call without symbol info, but resolution |
| 1077 | // can: if `Foo` resolves to a class, the call IS an instantiation. |
| 1078 | if (kind === 'calls') { |
| 1079 | const targetNode = this.queries.getNodeById(ref.targetNodeId); |
| 1080 | if (targetNode && (targetNode.kind === 'class' || targetNode.kind === 'struct')) { |
| 1081 | kind = 'instantiates'; |
| 1082 | } |
| 1083 | } |
| 1084 | |
| 1085 | return { |
| 1086 | source: ref.original.fromNodeId, |
| 1087 | target: ref.targetNodeId, |
| 1088 | kind, |
| 1089 | line: ref.original.line, |
| 1090 | column: ref.original.column, |
| 1091 | metadata: { |
| 1092 | confidence: ref.confidence, |
| 1093 | resolvedBy: ref.resolvedBy, |
| 1094 | // The ORIGINAL reference text (and kind, when edge-kind promotion |
| 1095 | // rewrote it — calls→instantiates, extends→implements, |
| 1096 | // function_ref→references). If this edge's target is later removed |
| 1097 | // by a re-index, the edge is resurrected as exactly this ref and |
| 1098 | // re-resolved (#1240 removal case) — a faithful resurrection, so |
| 1099 | // re-resolution can never bind anywhere a full re-index wouldn't. |
| 1100 | // Reconstruction from the target node's name instead would strip |
| 1101 | // receiver/qualifier context (`h.greet` → `greet`) and risk a |
| 1102 | // wrong rebind; edges without refName (pre-#1240, synthesized) are |
| 1103 | // deliberately NOT resurrected for the same reason. |
| 1104 | refName: ref.original.referenceName, |
| 1105 | ...(ref.original.referenceKind !== kind ? { refKind: ref.original.referenceKind } : {}), |
| 1106 | // Uniform marker for function-as-value edges (#756), regardless of |
| 1107 | // which strategy resolved them (import vs matchFunctionRef) — lets |
| 1108 | // tooling label "callback registration" and lets validation diff |
| 1109 | // exactly the edges this feature added. |
no test coverage detected