(node: SyntaxNode)
| 3688 | } |
| 3689 | |
| 3690 | private extractCall(node: SyntaxNode): void { |
| 3691 | if (this.nodeStack.length === 0) return; |
| 3692 | |
| 3693 | const callerId = this.nodeStack[this.nodeStack.length - 1]; |
| 3694 | if (!callerId) return; |
| 3695 | |
| 3696 | // VB.NET: `foo(args)` is syntactically ambiguous between a call and an |
| 3697 | // index read, so the grammar parses non-empty parens as |
| 3698 | // array_access_expression (field `array`, not `function`) — even Roslyn |
| 3699 | // parses both as InvocationExpression and resolves during binding. Treat |
| 3700 | // all three shapes as call sites: the callee is the member/identifier |
| 3701 | // under the array/function field, qualified with a simple-identifier |
| 3702 | // receiver for resolution. Index reads on collections simply never |
| 3703 | // resolve to a callable, so they cost nothing. |
| 3704 | if ( |
| 3705 | this.language === 'vbnet' && |
| 3706 | (node.type === 'array_access_expression' || |
| 3707 | node.type === 'invocation_expression' || |
| 3708 | node.type === 'generic_invocation_expression') |
| 3709 | ) { |
| 3710 | const fn = getChildByField(node, 'function') || getChildByField(node, 'array'); |
| 3711 | if (!fn) return; |
| 3712 | let calleeName = ''; |
| 3713 | if (fn.type === 'member_access_expression') { |
| 3714 | const member = getChildByField(fn, 'member'); |
| 3715 | const memberName = member ? getNodeText(member, this.source) : ''; |
| 3716 | if (!memberName) return; |
| 3717 | const receiver = getChildByField(fn, 'object'); |
| 3718 | const SKIP = new Set(['me', 'mybase', 'myclass']); |
| 3719 | if (receiver && receiver.type === 'identifier' && !SKIP.has(getNodeText(receiver, this.source).toLowerCase())) { |
| 3720 | calleeName = `${getNodeText(receiver, this.source)}.${memberName}`; |
| 3721 | } else { |
| 3722 | calleeName = memberName; |
| 3723 | } |
| 3724 | } else if (fn.type === 'identifier') { |
| 3725 | calleeName = getNodeText(fn, this.source); |
| 3726 | } else { |
| 3727 | return; // parenthesized/chained receivers: no static name to link |
| 3728 | } |
| 3729 | if (calleeName) { |
| 3730 | this.unresolvedReferences.push({ |
| 3731 | fromNodeId: callerId, |
| 3732 | referenceName: calleeName, |
| 3733 | referenceKind: 'calls', |
| 3734 | line: node.startPosition.row + 1, |
| 3735 | column: node.startPosition.column, |
| 3736 | }); |
| 3737 | } |
| 3738 | return; |
| 3739 | } |
| 3740 | |
| 3741 | // Erlang: a local call is `call(expr: atom, args)`; a remote call nests it |
| 3742 | // under `remote(module: remote_module, fun: call)` — the module qualifier |
| 3743 | // lives on the PARENT. Remote calls are emitted as `mod::fn`, which is |
| 3744 | // byte-identical to the qualifiedName the module namespace gives every |
| 3745 | // function (see packageTypes in languages/erlang.ts), so they resolve via |
| 3746 | // matchByQualifiedName. A var/macro callee or module (`F(X)`, `?M(X)`, |
| 3747 | // `Mod:handle(X)`) has no static target — except `?MODULE:fn(X)`, which the |
no test coverage detected