* Create a Node object
(
kind: NodeKind,
name: string,
node: SyntaxNode,
extra?: Partial<Node>
)
| 1206 | * Create a Node object |
| 1207 | */ |
| 1208 | private createNode( |
| 1209 | kind: NodeKind, |
| 1210 | name: string, |
| 1211 | node: SyntaxNode, |
| 1212 | extra?: Partial<Node> |
| 1213 | ): Node | null { |
| 1214 | // Skip nodes with empty/missing names — they are not meaningful symbols |
| 1215 | // and would cause FK violations when edges reference them (see issue #42) |
| 1216 | if (!name) { |
| 1217 | return null; |
| 1218 | } |
| 1219 | |
| 1220 | const id = generateNodeId(this.filePath, kind, name, node.startPosition.row + 1); |
| 1221 | |
| 1222 | // Some grammars (e.g. Dart) model a function/method body as a *sibling* of |
| 1223 | // the signature node, so the declaration node's own range is just the |
| 1224 | // signature line. Extend endLine to the resolved body when it sits beyond |
| 1225 | // the node so the node spans its body — required for any body-level analysis |
| 1226 | // (callees, the callback synthesizer's body scan, context slices). Guarded to |
| 1227 | // only ever extend: for child-body grammars the body is within range (no-op). |
| 1228 | let endLine = node.endPosition.row + 1; |
| 1229 | if (kind === 'function' || kind === 'method') { |
| 1230 | const body = this.extractor?.resolveBody?.(node, this.extractor.bodyField); |
| 1231 | if (body && body.endPosition.row + 1 > endLine) { |
| 1232 | endLine = body.endPosition.row + 1; |
| 1233 | } |
| 1234 | } |
| 1235 | |
| 1236 | const newNode: Node = { |
| 1237 | id, |
| 1238 | kind, |
| 1239 | name, |
| 1240 | qualifiedName: this.buildQualifiedName(name), |
| 1241 | filePath: this.filePath, |
| 1242 | language: this.language, |
| 1243 | startLine: node.startPosition.row + 1, |
| 1244 | endLine, |
| 1245 | startColumn: node.startPosition.column, |
| 1246 | endColumn: node.endPosition.column, |
| 1247 | updatedAt: Date.now(), |
| 1248 | ...extra, |
| 1249 | }; |
| 1250 | |
| 1251 | // Persist extra symbol-level modifiers (e.g. Kotlin `expect`/`actual`) onto |
| 1252 | // the node's decorators list so the resolver can pair multiplatform |
| 1253 | // declarations with their implementations. Merged, not overwritten, so a |
| 1254 | // language that also captures real annotations keeps both. |
| 1255 | const mods = this.extractor?.extractModifiers?.(node); |
| 1256 | if (mods && mods.length > 0) { |
| 1257 | newNode.decorators = [...(newNode.decorators ?? []), ...mods]; |
| 1258 | } |
| 1259 | |
| 1260 | this.nodes.push(newNode); |
| 1261 | |
| 1262 | // Add containment edge from parent |
| 1263 | if (this.nodeStack.length > 0) { |
| 1264 | const parentId = this.nodeStack[this.nodeStack.length - 1]; |
| 1265 | if (parentId) { |
no test coverage detected