| 151 | * Ported from runner's pipeline.ts processSymbol(). |
| 152 | */ |
| 153 | export function processSymbol( |
| 154 | symbol: CodeSymbol, |
| 155 | parentId: string, |
| 156 | language: string, |
| 157 | registries: Registries, |
| 158 | callInfos: CallInfo[], |
| 159 | nodes: GraphNode[], |
| 160 | rels: GraphRelationship[], |
| 161 | emittedNodeIds: Set<string>, |
| 162 | ): SymbolNode | null { |
| 163 | const fileId = parentId.split('::')[0]; |
| 164 | const sig = symbol.typeSignature ?? ''; |
| 165 | const namePart = symbol.receiverType |
| 166 | ? `${symbol.receiverType}.${symbol.name}${sig}` |
| 167 | : `${symbol.name}${sig}`; |
| 168 | const nodeId = `${parentId}::${namePart}`; |
| 169 | |
| 170 | // Guard against duplicate node IDs (e.g. C++ constructors sharing class name) |
| 171 | if (emittedNodeIds.has(nodeId)) { |
| 172 | return null; |
| 173 | } |
| 174 | emittedNodeIds.add(nodeId); |
| 175 | |
| 176 | if (symbol.kind === 'class') { |
| 177 | const props: Record<string, unknown> = { |
| 178 | language, |
| 179 | startLine: symbol.startLine, |
| 180 | endLine: symbol.endLine, |
| 181 | }; |
| 182 | if (symbol.signature) props.signature = symbol.signature; |
| 183 | if (symbol.superclasses) props.superclasses = symbol.superclasses; |
| 184 | if (symbol.interfaces) props.interfaces = symbol.interfaces; |
| 185 | if (symbol.subtype) props.kind = symbol.subtype; |
| 186 | if (symbol.docs) props.docs = symbol.docs; |
| 187 | |
| 188 | nodes.push({ |
| 189 | id: nodeId, |
| 190 | type: 'Class', |
| 191 | name: symbol.name, |
| 192 | properties: props, |
| 193 | }); |
| 194 | |
| 195 | rels.push({ |
| 196 | id: `${parentId}->DEFINES->${nodeId}`, |
| 197 | type: 'DEFINES', |
| 198 | source_id: parentId, |
| 199 | target_id: nodeId, |
| 200 | }); |
| 201 | |
| 202 | const symbolNode: SymbolNode = { |
| 203 | id: nodeId, |
| 204 | name: symbol.name, |
| 205 | kind: 'class', |
| 206 | fileId, |
| 207 | parentId, |
| 208 | language, |
| 209 | receiverVar: null, |
| 210 | receiverType: null, |